odfdo
odfdo
Python library for OpenDocument format (ODF)

odfdo is a Python3 library implementing the ISO/IEC 26300 OpenDocument Format
standard.
Project: https://github.com/jdum/odfdo
Author: jerome.dumonteil@gmail.com
License: Apache License, Version 2.0
odfdo is a derivative work of the former lpod-python project.
Installation
Installation from Pypi (recommended):
pip install odfdo
Installation from sources (requiring setuptools):
pip install .
After installation from sources, you can check everything is working (some requirements: pytest, Pillow, ...):
pytest
The tests should run for a few seconds or minutes and issue no error.
Usage
from odfdo import Document, Paragraph
doc = Document('text')
doc.body.append(Paragraph("Hello world!"))
doc.save("hello.odt")
tl;dr
'Intended Audience :: Developers'
Documentation
There is no detailed documentation or tutorial, but:
- the
recipesfolder contains more than 50 working sample scripts, - the
docfolder contains an auto generated documentation.
When installing odfdo, a few scripts are installed:
odfdo-diff: show a diff between two .odt document.odfdo-folder: convert standard ODF file to folder and files, and reverse.odfdo-show: dump text from an ODF file to the standard output, and optionally styles and meta informations.odfdo-styles: command line interface tool to manipulate styles of ODF files.odfdo-replace: find a pattern (regex) in an ODF file and replace by some string.odfdo-highlight: highlight the text matching a pattern (regex) in an ODF file.odfdo-headers: print the headers of an ODF file.
About styles: the best way to apply style is by merging styles from a template
document into your generated document (See odfdo-styles script).
Styles are a complex matter in ODF, so trying to generate styles programmatically
is not recommended.
Limitations
odfdo is intended to facilitate the generation of ODF documents,
nevertheless a basic knowledge of the ODF format is necessary.
ODF document rendering can vary greatly from software to software. Especially the "styles" of the document allow an adaptation of the rendering for a particular software.
The best (only ?) way to apply style is by merging styles from a template document into your generated document.
Related project
I you work on .ods files (spreadsheet), you may be interested by these scripts that use this library to parse/generate .ods files: https://github.com/jdum/odsgenerator and https://github.com/jdum/odsparsator
Changes from former lpod library
lpod-python was written in 2009-2010 as a Python 2.x library,
see: https://github.com/lpod/lpod-python
odfdo is an adaptation of this former project. odfdo main changes from lpod:
odfdorequires Python version 3.9 to 3.12. For Python 3.6 to 3.8 see previous releases.- API change: more pythonic.
- include recipes.
- use Apache 2.0 license.
1# Copyright 2018-2024 Jérôme Dumonteil 2# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16# 17# Authors (odfdo project): jerome.dumonteil@gmail.com 18# The odfdo project is a derivative work of the lpod-python project: 19# https://github.com/lpod/lpod-python 20# Authors: David Versmisse <david.versmisse@itaapy.com> 21# Hervé Cauwelier <herve@itaapy.com> 22# Romain Gauthier <romain@itaapy.com> 23""" 24.. include:: ../README.md 25""" 26 27__all__ = [ 28 "AnimPar", 29 "AnimSeq", 30 "AnimTransFilter", 31 "Annotation", 32 "AnnotationEnd", 33 "BackgroundImage", 34 "Bookmark", 35 "BookmarkEnd", 36 "BookmarkStart", 37 "Cell", 38 "ChangeInfo", 39 "Column", 40 "ConnectorShape", 41 "Container", 42 "Content", 43 "Content", 44 "Document", 45 "DrawFillImage", 46 "DrawGroup", 47 "DrawImage", 48 "DrawPage", 49 "Element", 50 "ElementTyped", 51 "EllipseShape", 52 "FIRST_CHILD", 53 "Frame", 54 "Header", 55 "HeaderRows", 56 "IndexTitle", 57 "IndexTitleTemplate", 58 "LAST_CHILD", 59 "LineBreak", 60 "LineShape", 61 "Link", 62 "List", 63 "ListItem", 64 "Manifest", 65 "Meta", 66 "NEXT_SIBLING", 67 "NamedRange", 68 "Note", 69 "PREV_SIBLING", 70 "Paragraph", 71 "PageBreak", 72 "RectangleShape", 73 "Reference", 74 "ReferenceMark", 75 "ReferenceMarkEnd", 76 "ReferenceMarkStart", 77 "Row", 78 "RowGroup", 79 "Section", 80 "Spacer", 81 "Span", 82 "Style", 83 "Styles", 84 "TOC", 85 "Tab", 86 "TabStopStyle", 87 "Table", 88 "Text", 89 "TextChange", 90 "TextChangeEnd", 91 "TextChangeStart", 92 "TextChangedRegion", 93 "TextDeletion", 94 "TextFormatChange", 95 "TextInsertion", 96 "TocEntryTemplate", 97 "TrackedChanges", 98 "UserDefined", 99 "UserFieldDecl", 100 "UserFieldDecls", 101 "UserFieldGet", 102 "UserFieldInput", 103 "VarChapter", 104 "VarCreationDate", 105 "VarCreationTime", 106 "VarDate", 107 "VarDecl", 108 "VarDecls", 109 "VarDescription", 110 "VarFileName", 111 "VarGet", 112 "VarInitialCreator", 113 "VarKeywords", 114 "VarPageCount", 115 "VarPageNumber", 116 "VarSet", 117 "VarSubject", 118 "VarTime", 119 "VarTitle", 120 "XmlPart", 121 "__version__", 122 "create_table_cell_style", 123 "default_boolean_style", 124 "default_currency_style", 125 "default_date_style", 126 "default_frame_position_style", 127 "default_number_style", 128 "default_percentage_style", 129 "default_time_style", 130 "default_toc_level_style", 131 "hex2rgb", 132 "make_table_cell_border_string", 133 "rgb2hex", 134] 135 136 137from .bookmark import Bookmark, BookmarkEnd, BookmarkStart 138from .cell import Cell 139from .container import Container 140from .content import Content 141from .document import Document 142from .draw_page import DrawPage 143from .element import FIRST_CHILD, LAST_CHILD, NEXT_SIBLING, PREV_SIBLING, Element, Text 144from .element_typed import ElementTyped 145from .frame import Frame, default_frame_position_style 146from .header import Header 147from .header_rows import HeaderRows 148from .image import DrawFillImage, DrawImage 149from .link import Link 150from .list import List, ListItem 151from .manifest import Manifest 152from .meta import Meta 153from .note import Annotation, AnnotationEnd, Note 154from .paragraph import LineBreak, PageBreak, Paragraph, Spacer, Span, Tab 155from .reference import Reference, ReferenceMark, ReferenceMarkEnd, ReferenceMarkStart 156from .section import Section 157from .shapes import ConnectorShape, DrawGroup, EllipseShape, LineShape, RectangleShape 158from .smil import AnimPar, AnimSeq, AnimTransFilter 159from .style import ( 160 BackgroundImage, 161 Style, 162 create_table_cell_style, 163 default_boolean_style, 164 default_currency_style, 165 default_date_style, 166 default_number_style, 167 default_percentage_style, 168 default_time_style, 169 hex2rgb, 170 make_table_cell_border_string, 171 rgb2hex, 172) 173from .styles import Styles 174from .table import Column, NamedRange, Row, RowGroup, Table 175from .toc import ( 176 TOC, 177 IndexTitle, 178 IndexTitleTemplate, 179 TabStopStyle, 180 TocEntryTemplate, 181 default_toc_level_style, 182) 183from .tracked_changes import ( 184 ChangeInfo, 185 TextChange, 186 TextChangedRegion, 187 TextChangeEnd, 188 TextChangeStart, 189 TextDeletion, 190 TextFormatChange, 191 TextInsertion, 192 TrackedChanges, 193) 194from .variable import ( 195 UserDefined, 196 UserFieldDecl, 197 UserFieldDecls, 198 UserFieldGet, 199 UserFieldInput, 200 VarChapter, 201 VarCreationDate, 202 VarCreationTime, 203 VarDate, 204 VarDecl, 205 VarDecls, 206 VarDescription, 207 VarFileName, 208 VarGet, 209 VarInitialCreator, 210 VarKeywords, 211 VarPageCount, 212 VarPageNumber, 213 VarSet, 214 VarSubject, 215 VarTime, 216 VarTitle, 217) 218from .version import __version__ 219from .xmlpart import XmlPart
32class AnimPar(Element): 33 """A container for SMIL Presentation Animations. 34 35 Arguments: 36 37 presentation_node_type -- default, on-click, with-previous, 38 after-previous, timing-root, main-sequence 39 and interactive-sequence 40 41 smil_begin -- indefinite, 10s, [id].click, [id].begin 42 """ 43 44 _tag = "anim:par" 45 _properties = ( 46 PropDef("presentation_node_type", "presentation:node-type"), 47 PropDef("smil_begin", "smil:begin"), 48 ) 49 50 def __init__( 51 self, 52 presentation_node_type: str | None = None, 53 smil_begin: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 super().__init__(**kwargs) 57 if self._do_init: 58 if presentation_node_type: 59 self.presentation_node_type = presentation_node_type 60 if smil_begin: 61 self.smil_begin = smil_begin
A container for SMIL Presentation Animations.
Arguments:
presentation_node_type -- default, on-click, with-previous,
after-previous, timing-root, main-sequence
and interactive-sequence
smil_begin -- indefinite, 10s, [id].click, [id].begin
50 def __init__( 51 self, 52 presentation_node_type: str | None = None, 53 smil_begin: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 super().__init__(**kwargs) 57 if self._do_init: 58 if presentation_node_type: 59 self.presentation_node_type = presentation_node_type 60 if smil_begin: 61 self.smil_begin = smil_begin
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
67class AnimSeq(Element): 68 """TA container for SMIL Presentation Animations. Animations 69 inside this block are executed after the slide has executed its initial 70 transition. 71 72 Arguments: 73 74 presentation_node_type -- default, on-click, with-previous, 75 after-previous, timing-root, main-sequence 76 and interactive-sequence 77 """ 78 79 _tag = "anim:seq" 80 _properties = (PropDef("presentation_node_type", "presentation:node-type"),) 81 82 def __init__( 83 self, 84 presentation_node_type: str | None = None, 85 **kwargs: Any, 86 ) -> None: 87 super().__init__(**kwargs) 88 if self._do_init and presentation_node_type: 89 self.presentation_node_type = presentation_node_type
TA container for SMIL Presentation Animations. Animations inside this block are executed after the slide has executed its initial transition.
Arguments:
presentation_node_type -- default, on-click, with-previous,
after-previous, timing-root, main-sequence
and interactive-sequence
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
95class AnimTransFilter(Element): 96 """ 97 Class to make a beautiful transition between two frames. 98 99 Arguments: 100 smil_dur -- XXX complete me 101 102 smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ 103 smil-transitions.html#TransitionEffects-Appendix 104 to get a list of all types/subtypes 105 106 smil_direction -- forward, reverse 107 108 smil_fadeColor -- forward, reverse 109 110 smil_mode -- in, out 111 """ 112 113 _tag = "anim:transitionFilter" 114 _properties = ( 115 PropDef("smil_dur", "smil:dur"), 116 PropDef("smil_type", "smil:type"), 117 PropDef("smil_subtype", "smil:subtype"), 118 PropDef("smil_direction", "smil:direction"), 119 PropDef("smil_fadeColor", "smil:fadeColor"), 120 PropDef("smil_mode", "smil:mode"), 121 ) 122 123 def __init__( 124 self, 125 smil_dur: str | None = None, 126 smil_type: str | None = None, 127 smil_subtype: str | None = None, 128 smil_direction: str | None = None, 129 smil_fadeColor: str | None = None, 130 smil_mode: str | None = None, 131 **kwargs: Any, 132 ) -> None: 133 super().__init__(**kwargs) 134 if self._do_init: 135 if smil_dur: 136 self.smil_dur = smil_dur 137 if smil_type: 138 self.smil_type = smil_type 139 if smil_subtype: 140 self.smil_subtype = smil_subtype 141 if smil_direction: 142 self.smil_direction = smil_direction 143 if smil_fadeColor: 144 self.smil_fadeColor = smil_fadeColor 145 if smil_mode: 146 self.smil_mode = smil_mode
Class to make a beautiful transition between two frames.
Arguments: smil_dur -- XXX complete me
smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ smil-transitions.html#TransitionEffects-Appendix to get a list of all types/subtypes
smil_direction -- forward, reverse
smil_fadeColor -- forward, reverse
smil_mode -- in, out
123 def __init__( 124 self, 125 smil_dur: str | None = None, 126 smil_type: str | None = None, 127 smil_subtype: str | None = None, 128 smil_direction: str | None = None, 129 smil_fadeColor: str | None = None, 130 smil_mode: str | None = None, 131 **kwargs: Any, 132 ) -> None: 133 super().__init__(**kwargs) 134 if self._do_init: 135 if smil_dur: 136 self.smil_dur = smil_dur 137 if smil_type: 138 self.smil_type = smil_type 139 if smil_subtype: 140 self.smil_subtype = smil_subtype 141 if smil_direction: 142 self.smil_direction = smil_direction 143 if smil_fadeColor: 144 self.smil_fadeColor = smil_fadeColor 145 if smil_mode: 146 self.smil_mode = smil_mode
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
147class Annotation(Element): 148 """Annotation element credited to the given creator with the 149 given text, optionally dated (current date by default). 150 If name not provided and some parent is provided, the name is 151 autogenerated. 152 153 Arguments: 154 155 text -- str or odf_element 156 157 creator -- str 158 159 date -- datetime 160 161 name -- str 162 163 parent -- Element 164 """ 165 166 _tag = "office:annotation" 167 _properties = ( 168 PropDef("name", "office:name"), 169 PropDef("note_id", "text:id"), 170 ) 171 172 def __init__( 173 self, 174 text_or_element: Element | str | None = None, 175 creator: str | None = None, 176 date: datetime | None = None, 177 name: str | None = None, 178 parent: Element | None = None, 179 **kwargs: Any, 180 ) -> None: 181 # fixme : use offset 182 # TODO allow paragraph and text styles 183 super().__init__(**kwargs) 184 185 if self._do_init: 186 self.note_body = text_or_element # type:ignore 187 if creator: 188 self.dc_creator = creator 189 if date is None: 190 date = datetime.now() 191 self.dc_date = date 192 if not name: 193 name = get_unique_office_name(parent) 194 self.name = name 195 196 @property 197 def note_body(self) -> str: 198 return self.text_content 199 200 @note_body.setter 201 def note_body(self, text_or_element: Element | str | None) -> None: 202 if text_or_element is None: 203 self.text_content = "" 204 elif isinstance(text_or_element, str): 205 self.text_content = text_or_element 206 elif isinstance(text_or_element, Element): 207 self.clear() 208 self.append(text_or_element) 209 else: 210 raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"') 211 212 @property 213 def start(self) -> Element: 214 """Return self.""" 215 return self 216 217 @property 218 def end(self) -> Element | None: 219 """Return the corresponding annotation-end tag or None.""" 220 name = self.name 221 parent = self.parent 222 if parent is None: 223 raise ValueError("Can't find end tag: no parent available") 224 body = self.document_body 225 if not body: 226 body = parent 227 return body.get_annotation_end(name=name) 228 229 def get_annotated( 230 self, 231 as_text: bool = False, 232 no_header: bool = True, 233 clean: bool = True, 234 ) -> Element | list | str | None: 235 """Returns the annotated content from an annotation. 236 237 If no content exists (single position annotation or annotation-end not 238 found), returns [] (or "" if text flag is True). 239 If as_text is True: returns the text content. 240 If clean is True: suppress unwanted tags (deletions marks, ...) 241 If no_header is True: existing text:h are changed in text:p 242 By default: returns a list of odf_element, cleaned and without headers. 243 244 Arguments: 245 246 as_text -- boolean 247 248 clean -- boolean 249 250 no_header -- boolean 251 252 Return: list or Element or text or None 253 """ 254 end = self.end 255 if end is None: 256 if as_text: 257 return "" 258 return None 259 body = self.document_body 260 if not body: 261 body = self.root 262 return body.get_between( 263 self, end, as_text=as_text, no_header=no_header, clean=clean 264 ) 265 266 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 267 """Delete the given element from the XML tree. If no element is given, 268 "self" is deleted. The XML library may allow to continue to use an 269 element now "orphan" as long as you have a reference to it. 270 271 For Annotation : delete the annotation-end tag if exists. 272 273 Arguments: 274 275 child -- Element or None 276 """ 277 if child is not None: # act like normal delete 278 super().delete(child) 279 return 280 end = self.end 281 if end: 282 end.delete() 283 # act like normal delete 284 super().delete() 285 286 def check_validity(self) -> None: 287 if not self.note_body: 288 raise ValueError("Annotation must have a body") 289 if not self.dc_creator: 290 raise ValueError("Annotation must have a creator") 291 if not self.dc_date: 292 self.dc_date = datetime.now()
Annotation element credited to the given creator with the given text, optionally dated (current date by default). If name not provided and some parent is provided, the name is autogenerated.
Arguments:
text -- str or odf_element
creator -- str
date -- datetime
name -- str
parent -- Element
172 def __init__( 173 self, 174 text_or_element: Element | str | None = None, 175 creator: str | None = None, 176 date: datetime | None = None, 177 name: str | None = None, 178 parent: Element | None = None, 179 **kwargs: Any, 180 ) -> None: 181 # fixme : use offset 182 # TODO allow paragraph and text styles 183 super().__init__(**kwargs) 184 185 if self._do_init: 186 self.note_body = text_or_element # type:ignore 187 if creator: 188 self.dc_creator = creator 189 if date is None: 190 date = datetime.now() 191 self.dc_date = date 192 if not name: 193 name = get_unique_office_name(parent) 194 self.name = name
217 @property 218 def end(self) -> Element | None: 219 """Return the corresponding annotation-end tag or None.""" 220 name = self.name 221 parent = self.parent 222 if parent is None: 223 raise ValueError("Can't find end tag: no parent available") 224 body = self.document_body 225 if not body: 226 body = parent 227 return body.get_annotation_end(name=name)
Return the corresponding annotation-end tag or None.
229 def get_annotated( 230 self, 231 as_text: bool = False, 232 no_header: bool = True, 233 clean: bool = True, 234 ) -> Element | list | str | None: 235 """Returns the annotated content from an annotation. 236 237 If no content exists (single position annotation or annotation-end not 238 found), returns [] (or "" if text flag is True). 239 If as_text is True: returns the text content. 240 If clean is True: suppress unwanted tags (deletions marks, ...) 241 If no_header is True: existing text:h are changed in text:p 242 By default: returns a list of odf_element, cleaned and without headers. 243 244 Arguments: 245 246 as_text -- boolean 247 248 clean -- boolean 249 250 no_header -- boolean 251 252 Return: list or Element or text or None 253 """ 254 end = self.end 255 if end is None: 256 if as_text: 257 return "" 258 return None 259 body = self.document_body 260 if not body: 261 body = self.root 262 return body.get_between( 263 self, end, as_text=as_text, no_header=no_header, clean=clean 264 )
Returns the annotated content from an annotation.
If no content exists (single position annotation or annotation-end not found), returns [] (or "" if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of odf_element, cleaned and without headers.
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text or None
266 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 267 """Delete the given element from the XML tree. If no element is given, 268 "self" is deleted. The XML library may allow to continue to use an 269 element now "orphan" as long as you have a reference to it. 270 271 For Annotation : delete the annotation-end tag if exists. 272 273 Arguments: 274 275 child -- Element or None 276 """ 277 if child is not None: # act like normal delete 278 super().delete(child) 279 return 280 end = self.end 281 if end: 282 end.delete() 283 # act like normal delete 284 super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For Annotation : delete the annotation-end tag if exists.
Arguments:
child -- Element or None
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
298class AnnotationEnd(Element): 299 """AnnotationEnd: the "office:annotation-end" element may be used to 300 define the end of a text range of document content that spans element 301 boundaries. In that case, an "office:annotation" element shall precede 302 the "office:annotation-end" element. Both elements shall have the same 303 value for their office:name attribute. The "office:annotation-end" element 304 shall be preceded by an "office:annotation" element that has the same 305 value for its office:name attribute as the "office:annotation-end" 306 element. An "office:annotation-end" element without a preceding 307 "office:annotation" element that has the same name assigned is ignored. 308 """ 309 310 _tag = "office:annotation-end" 311 _properties = (PropDef("name", "office:name"),) 312 313 def __init__( 314 self, 315 annotation: Element | None = None, 316 name: str | None = None, 317 **kwargs: Any, 318 ) -> None: 319 """Initialize an AnnotationEnd element. Either annotation or name must be 320 provided to have proper reference for the annotation-end. 321 322 Arguments: 323 324 annotation -- odf_annotation element 325 326 name -- str 327 """ 328 # fixme : use offset 329 # TODO allow paragraph and text styles 330 super().__init__(**kwargs) 331 if self._do_init: 332 if annotation: 333 name = annotation.name # type: ignore 334 if not name: 335 raise ValueError("Annotation-end must have a name") 336 self.name = name 337 338 @property 339 def start(self) -> Element | None: 340 """Return the corresponding annotation starting tag or None.""" 341 name = self.name 342 parent = self.parent 343 if parent is None: 344 raise ValueError("Can't find start tag: no parent available") 345 body = self.document_body 346 if not body: 347 body = parent 348 return body.get_annotation(name=name) 349 350 @property 351 def end(self) -> Element: 352 """Return self.""" 353 return self
AnnotationEnd: the "office:annotation-end" element may be used to define the end of a text range of document content that spans element boundaries. In that case, an "office:annotation" element shall precede the "office:annotation-end" element. Both elements shall have the same value for their office:name attribute. The "office:annotation-end" element shall be preceded by an "office:annotation" element that has the same value for its office:name attribute as the "office:annotation-end" element. An "office:annotation-end" element without a preceding "office:annotation" element that has the same name assigned is ignored.
313 def __init__( 314 self, 315 annotation: Element | None = None, 316 name: str | None = None, 317 **kwargs: Any, 318 ) -> None: 319 """Initialize an AnnotationEnd element. Either annotation or name must be 320 provided to have proper reference for the annotation-end. 321 322 Arguments: 323 324 annotation -- odf_annotation element 325 326 name -- str 327 """ 328 # fixme : use offset 329 # TODO allow paragraph and text styles 330 super().__init__(**kwargs) 331 if self._do_init: 332 if annotation: 333 name = annotation.name # type: ignore 334 if not name: 335 raise ValueError("Annotation-end must have a name") 336 self.name = name
Initialize an AnnotationEnd element. Either annotation or name must be provided to have proper reference for the annotation-end.
Arguments:
annotation -- odf_annotation element
name -- str
338 @property 339 def start(self) -> Element | None: 340 """Return the corresponding annotation starting tag or None.""" 341 name = self.name 342 parent = self.parent 343 if parent is None: 344 raise ValueError("Can't find start tag: no parent available") 345 body = self.document_body 346 if not body: 347 body = parent 348 return body.get_annotation(name=name)
Return the corresponding annotation starting tag or None.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
1054class BackgroundImage(Style, DrawImage): 1055 _tag = "style:background-image" 1056 _properties: tuple[PropDef, ...] = ( 1057 PropDef("name", "style:name"), 1058 PropDef("display_name", "style:display-name"), 1059 PropDef("svg_font_family", "svg:font-family"), 1060 PropDef("font_family_generic", "style:font-family-generic"), 1061 PropDef("font_pitch", "style:font-pitch"), 1062 PropDef("position", "style:position", "background-image"), 1063 PropDef("repeat", "style:repeat", "background-image"), 1064 PropDef("opacity", "draw:opacity", "background-image"), 1065 PropDef("filter", "style:filter-name", "background-image"), 1066 PropDef("text_style", "text:style-name"), 1067 ) 1068 1069 def __init__( 1070 self, 1071 name: str | None = None, 1072 display_name: str | None = None, 1073 position: str | None = None, 1074 repeat: str | None = None, 1075 opacity: str | None = None, 1076 filter: str | None = None, # noqa: A002 1077 # Every other property 1078 **kwargs: Any, 1079 ): 1080 kwargs["family"] = "background-image" 1081 super().__init__(**kwargs) 1082 if self._do_init: 1083 kwargs.pop("tag", None) 1084 kwargs.pop("tag_or_elem", None) 1085 self.family = "background-image" 1086 if name: 1087 self.name = name 1088 if display_name: 1089 self.display_name = display_name 1090 if position: 1091 self.position = position 1092 if repeat: 1093 self.position = repeat 1094 if opacity: 1095 self.position = opacity 1096 if filter: 1097 self.position = filter 1098 # Every other properties 1099 for prop in BackgroundImage._properties: 1100 if prop.name in kwargs: 1101 self.set_style_attribute(prop.attr, kwargs[prop.name])
Style class for all these tags:
'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...
1069 def __init__( 1070 self, 1071 name: str | None = None, 1072 display_name: str | None = None, 1073 position: str | None = None, 1074 repeat: str | None = None, 1075 opacity: str | None = None, 1076 filter: str | None = None, # noqa: A002 1077 # Every other property 1078 **kwargs: Any, 1079 ): 1080 kwargs["family"] = "background-image" 1081 super().__init__(**kwargs) 1082 if self._do_init: 1083 kwargs.pop("tag", None) 1084 kwargs.pop("tag_or_elem", None) 1085 self.family = "background-image" 1086 if name: 1087 self.name = name 1088 if display_name: 1089 self.display_name = display_name 1090 if position: 1091 self.position = position 1092 if repeat: 1093 self.position = repeat 1094 if opacity: 1095 self.position = opacity 1096 if filter: 1097 self.position = filter 1098 # Every other properties 1099 for prop in BackgroundImage._properties: 1100 if prop.name in kwargs: 1101 self.set_style_attribute(prop.attr, kwargs[prop.name])
Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.
The display name is the name the user sees in an office application.
The parent_style is the name of the style this style will inherit from.
To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.
Arguments:
family -- 'paragraph', 'text', 'section', 'table', 'table-column',
'table-row', 'table-cell', 'table-page', 'chart',
'drawing-page', 'graphic', 'presentation',
'control', 'ruby', 'list', 'number', 'page-layout'
'font-face', or 'master-page'
name -- str
display_name -- str
parent_style -- str
area -- str
'text' Properties:
italic -- bool
bold -- bool
'paragraph' Properties:
master_page -- str
'master-page' Properties:
page_layout -- str
next_style -- str
'table-cell' Properties:
border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'
padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
'table-row' Properties:
height -- str, e.g. '5cm'
use_optimal_height -- bool
'table-column' Properties:
width -- str, e.g. '5cm'
break_before -- 'page', 'column' or 'auto'
break_after -- 'page', 'column' or 'auto'
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Style
- family
- get_properties
- set_properties
- del_properties
- set_background
- get_level_style
- set_level_style
- get_header_style
- set_header_style
- get_page_header
- set_page_header
- set_font
- page_layout
- next_style
- parent_style
- master_page
- style_type
- leader_style
- leader_text
- style_position
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
31class Bookmark(Element): 32 """ 33 Bookmark class for ODF "text:bookmark" 34 35 Arguments: 36 37 name -- str 38 """ 39 40 _tag = "text:bookmark" 41 _properties = (PropDef("name", "text:name"),) 42 43 def __init__(self, name: str = "", **kwargs: Any) -> None: 44 super().__init__(**kwargs) 45 if self._do_init: 46 self.name = name
Bookmark class for ODF "text:bookmark"
Arguments:
name -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
73class BookmarkEnd(Element): 74 """ 75 BookmarkEnd class for ODF "text:bookmark-end" 76 77 Arguments: 78 79 name -- str 80 """ 81 82 _tag = "text:bookmark-end" 83 _properties = (PropDef("name", "text:name"),) 84 85 def __init__(self, name: str = "", **kwargs: Any) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.name = name
BookmarkEnd class for ODF "text:bookmark-end"
Arguments:
name -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
52class BookmarkStart(Element): 53 """ 54 BookmarkStart class for ODF "text:bookmark-start" 55 56 Arguments: 57 58 name -- str 59 """ 60 61 _tag = "text:bookmark-start" 62 _properties = (PropDef("name", "text:name"),) 63 64 def __init__(self, name: str = "", **kwargs: Any) -> None: 65 super().__init__(**kwargs) 66 if self._do_init: 67 self.name = name
BookmarkStart class for ODF "text:bookmark-start"
Arguments:
name -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
44class Cell(ElementTyped): 45 """ "table:table-cell" table cell element.""" 46 47 _tag = "table:table-cell" 48 _caching = True 49 50 def __init__( 51 self, 52 value: Any = None, 53 text: str | None = None, 54 cell_type: str | None = None, 55 currency: str | None = None, 56 formula: str | None = None, 57 repeated: int | None = None, 58 style: str | None = None, 59 **kwargs: Any, 60 ) -> None: 61 """Create a cell element containing the given value. The textual 62 representation is automatically formatted but can be provided. Cell 63 type can be deduced as well, unless the number is a percentage or 64 currency. If cell type is "currency", the currency must be given. 65 The cell can be repeated on the given number of columns. 66 67 Arguments: 68 69 value -- bool, int, float, Decimal, date, datetime, str, 70 timedelta 71 72 text -- str 73 74 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 75 'string' or 'time' 76 77 currency -- three-letter str 78 79 repeated -- int 80 81 style -- str 82 """ 83 super().__init__(**kwargs) 84 self.x = None 85 self.y = None 86 if self._do_init: 87 self.set_value( 88 value, 89 text=text, 90 cell_type=cell_type, 91 currency=currency, 92 formula=formula, 93 ) 94 if repeated and repeated > 1: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 99 @property 100 def clone(self) -> Cell: 101 clone = Element.clone.fget(self) # type: ignore 102 clone.y = self.y 103 clone.x = self.x 104 if hasattr(self, "_tmap"): 105 if hasattr(self, "_rmap"): 106 clone._rmap = self._rmap[:] 107 clone._tmap = self._tmap[:] 108 clone._cmap = self._cmap[:] 109 return clone 110 111 @property 112 def value( 113 self, 114 ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None: 115 """Set / get the value of the cell. The type is read from the 116 'office:value-type' attribute of the cell. When setting the value, 117 the type of the value will determine the new value_type of the cell. 118 119 Warning: use this method for boolean, float or string only. 120 """ 121 value_type = self.get_attribute_string("office:value-type") 122 if value_type == "boolean": 123 return self.get_attribute("office:boolean-value") 124 if value_type in {"float", "percentage", "currency"}: 125 value_decimal = Decimal(str(self.get_attribute_string("office:value"))) 126 # Return 3 instead of 3.0 if possible 127 if int(value_decimal) == value_decimal: 128 return int(value_decimal) 129 return value_decimal 130 if value_type == "date": 131 value_str = str(self.get_attribute_string("office:date-value")) 132 if "T" in value_str: 133 return DateTime.decode(value_str) 134 return Date.decode(value_str) 135 if value_type == "time": 136 return Duration.decode(str(self.get_attribute_string("office:time-value"))) 137 if value_type == "string": 138 value = self.get_attribute_string("office:string-value") 139 if value is not None: 140 return value 141 value_list = [] 142 for para in self.get_elements("text:p"): 143 value_list.append(para.text_recursive) 144 return "\n".join(value_list) 145 return None 146 147 @value.setter 148 def value(self, value: str | bytes | bool | int | Float | Decimal | None) -> None: 149 self.clear() 150 if value is None: 151 return 152 if isinstance(value, (str, bytes)): 153 if isinstance(value, bytes): 154 value = bytes_to_str(value) 155 self.set_attribute("office:value-type", "string") 156 self.set_attribute("office:string-value", value) 157 self.text = value 158 return 159 if value is True or value is False: 160 self.set_attribute("office:value-type", "boolean") 161 value_bool = Boolean.encode(value) 162 self.set_attribute("office:boolean-value", value_bool) 163 self.text = value_bool 164 return 165 if isinstance(value, (int, Float, Decimal)): 166 self.set_attribute("office:value-type", "float") 167 value_str = str(value) 168 self.set_attribute("office:value", value_str) 169 self.text = value_str 170 return 171 raise TypeError(f"Unknown value type, try with set_value() : {value!r}") 172 173 @property 174 def float(self) -> Float: 175 """Set / get the value of the cell as a float (or 0.0).""" 176 for tag in ("office:value", "office:string-value", "office:boolean-value"): 177 read_attr = self.get_attribute(tag) 178 if isinstance(read_attr, str): 179 with contextlib.suppress(ValueError, TypeError): 180 return Float(read_attr) 181 return 0.0 182 183 @float.setter 184 def float(self, value: str | Float | int | Decimal) -> None: 185 try: 186 value_float = Float(value) 187 except (ValueError, TypeError): 188 value_float = 0.0 189 value_str = str(value_float) 190 self.clear() 191 self.set_attribute("office:value", value_str) 192 self.set_attribute("office:value-type", "float") 193 self.text = value_str 194 195 @property 196 def string(self) -> str: 197 """Set / get the value of the cell as a string (or '').""" 198 value = self.get_attribute_string("office:string-value") 199 if isinstance(value, str): 200 return value 201 return "" 202 203 @string.setter 204 def string( 205 self, 206 value: str | bytes | int | Float | Decimal | bool | None, # type: ignore 207 ) -> None: 208 self.clear() 209 if value is None: 210 value_str = "" 211 else: 212 value_str = str(value) 213 self.set_attribute("office:value-type", "string") 214 self.set_attribute("office:string-value", value_str) 215 self.text = value_str 216 217 def set_value( 218 self, 219 value: ( 220 str # type: ignore 221 | bytes 222 | Float 223 | int 224 | Decimal 225 | bool 226 | datetime 227 | date 228 | timedelta 229 | None 230 ), 231 text: str | None = None, 232 cell_type: str | None = None, 233 currency: str | None = None, 234 formula: str | None = None, 235 ) -> None: 236 """Set the cell state from the Python value type. 237 238 Text is how the cell is displayed. Cell type is guessed, 239 unless provided. 240 241 For monetary values, provide the name of the currency. 242 243 Arguments: 244 245 value -- Python type 246 247 text -- str 248 249 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 250 'currency' or 'percentage' 251 252 currency -- str 253 """ 254 self.clear() 255 text = self.set_value_and_type( 256 value=value, 257 text=text, 258 value_type=cell_type, 259 currency=currency, 260 ) 261 if text is not None: 262 self.text_content = text 263 if formula is not None: 264 self.formula = formula 265 266 @property 267 def type(self) -> str | None: 268 """Get / set the type of the cell: boolean, float, date, string 269 or time. 270 271 Return: str | None 272 """ 273 return self.get_attribute_string("office:value-type") 274 275 @type.setter 276 def type(self, cell_type: str) -> None: 277 self.set_attribute("office:value-type", cell_type) 278 279 @property 280 def currency(self) -> str | None: 281 """Get / set the currency used for monetary values. 282 283 Return: str | None 284 """ 285 return self.get_attribute_string("office:currency") 286 287 @currency.setter 288 def currency(self, currency: str) -> None: 289 self.set_attribute("office:currency", currency) 290 291 def _set_repeated(self, repeated: int | None) -> None: 292 """Internal only. Set the numnber of times the cell is repeated, or 293 None to delete. Without changing cache. 294 """ 295 if repeated is None or repeated < 2: 296 with contextlib.suppress(KeyError): 297 self.del_attribute("table:number-columns-repeated") 298 return 299 self.set_attribute("table:number-columns-repeated", str(repeated)) 300 301 @property 302 def repeated(self) -> int | None: 303 """Get / set the number of times the cell is repeated. 304 305 Always None when using the table API. 306 307 Return: int or None 308 """ 309 repeated = self.get_attribute("table:number-columns-repeated") 310 if repeated is None: 311 return None 312 return int(repeated) 313 314 @repeated.setter 315 def repeated(self, repeated: int | None) -> None: 316 self._set_repeated(repeated) 317 # update cache 318 child: Element = self 319 while True: 320 # look for Row, parent may be group of rows 321 upper = child.parent 322 if not upper: 323 # lonely cell 324 return 325 # parent may be group of rows, not table 326 if isinstance(upper, Element) and upper._tag == "table:table-row": 327 break 328 child = upper 329 # fixme : need to optimize this 330 if isinstance(upper, Element) and upper._tag == "table:table-row": 331 upper._compute_row_cache() 332 333 @property 334 def style(self) -> str | None: 335 """Get / set the style of the cell itself. 336 337 Return: str | None 338 """ 339 return self.get_attribute_string("table:style-name") 340 341 @style.setter 342 def style(self, style: str | Element) -> None: 343 self.set_style_attribute("table:style-name", style) 344 345 @property 346 def formula(self) -> str | None: 347 """Get / set the formula of the cell, or None if undefined. 348 349 The formula is not interpreted in any way. 350 351 Return: str | None 352 """ 353 return self.get_attribute_string("table:formula") 354 355 @formula.setter 356 def formula(self, formula: str | None) -> None: 357 self.set_attribute("table:formula", formula) 358 359 def is_empty(self, aggressive: bool = False) -> bool: 360 if self.value is not None or self.children: 361 return False 362 if not aggressive and self.style is not None: 363 return False 364 return True 365 366 def _is_spanned(self) -> bool: 367 if self.tag == "table:covered-table-cell": 368 return True 369 if self.get_attribute("table:number-columns-spanned") is not None: 370 return True 371 if self.get_attribute("table:number-rows-spanned") is not None: 372 return True 373 return False
"table:table-cell" table cell element.
50 def __init__( 51 self, 52 value: Any = None, 53 text: str | None = None, 54 cell_type: str | None = None, 55 currency: str | None = None, 56 formula: str | None = None, 57 repeated: int | None = None, 58 style: str | None = None, 59 **kwargs: Any, 60 ) -> None: 61 """Create a cell element containing the given value. The textual 62 representation is automatically formatted but can be provided. Cell 63 type can be deduced as well, unless the number is a percentage or 64 currency. If cell type is "currency", the currency must be given. 65 The cell can be repeated on the given number of columns. 66 67 Arguments: 68 69 value -- bool, int, float, Decimal, date, datetime, str, 70 timedelta 71 72 text -- str 73 74 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 75 'string' or 'time' 76 77 currency -- three-letter str 78 79 repeated -- int 80 81 style -- str 82 """ 83 super().__init__(**kwargs) 84 self.x = None 85 self.y = None 86 if self._do_init: 87 self.set_value( 88 value, 89 text=text, 90 cell_type=cell_type, 91 currency=currency, 92 formula=formula, 93 ) 94 if repeated and repeated > 1: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style
Create a cell element containing the given value. The textual representation is automatically formatted but can be provided. Cell type can be deduced as well, unless the number is a percentage or currency. If cell type is "currency", the currency must be given. The cell can be repeated on the given number of columns.
Arguments:
value -- bool, int, float, Decimal, date, datetime, str,
timedelta
text -- str
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
repeated -- int
style -- str
99 @property 100 def clone(self) -> Cell: 101 clone = Element.clone.fget(self) # type: ignore 102 clone.y = self.y 103 clone.x = self.x 104 if hasattr(self, "_tmap"): 105 if hasattr(self, "_rmap"): 106 clone._rmap = self._rmap[:] 107 clone._tmap = self._tmap[:] 108 clone._cmap = self._cmap[:] 109 return clone
111 @property 112 def value( 113 self, 114 ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None: 115 """Set / get the value of the cell. The type is read from the 116 'office:value-type' attribute of the cell. When setting the value, 117 the type of the value will determine the new value_type of the cell. 118 119 Warning: use this method for boolean, float or string only. 120 """ 121 value_type = self.get_attribute_string("office:value-type") 122 if value_type == "boolean": 123 return self.get_attribute("office:boolean-value") 124 if value_type in {"float", "percentage", "currency"}: 125 value_decimal = Decimal(str(self.get_attribute_string("office:value"))) 126 # Return 3 instead of 3.0 if possible 127 if int(value_decimal) == value_decimal: 128 return int(value_decimal) 129 return value_decimal 130 if value_type == "date": 131 value_str = str(self.get_attribute_string("office:date-value")) 132 if "T" in value_str: 133 return DateTime.decode(value_str) 134 return Date.decode(value_str) 135 if value_type == "time": 136 return Duration.decode(str(self.get_attribute_string("office:time-value"))) 137 if value_type == "string": 138 value = self.get_attribute_string("office:string-value") 139 if value is not None: 140 return value 141 value_list = [] 142 for para in self.get_elements("text:p"): 143 value_list.append(para.text_recursive) 144 return "\n".join(value_list) 145 return None
Set / get the value of the cell. The type is read from the 'office:value-type' attribute of the cell. When setting the value, the type of the value will determine the new value_type of the cell.
Warning: use this method for boolean, float or string only.
173 @property 174 def float(self) -> Float: 175 """Set / get the value of the cell as a float (or 0.0).""" 176 for tag in ("office:value", "office:string-value", "office:boolean-value"): 177 read_attr = self.get_attribute(tag) 178 if isinstance(read_attr, str): 179 with contextlib.suppress(ValueError, TypeError): 180 return Float(read_attr) 181 return 0.0
Set / get the value of the cell as a float (or 0.0).
195 @property 196 def string(self) -> str: 197 """Set / get the value of the cell as a string (or '').""" 198 value = self.get_attribute_string("office:string-value") 199 if isinstance(value, str): 200 return value 201 return ""
Set / get the value of the cell as a string (or '').
217 def set_value( 218 self, 219 value: ( 220 str # type: ignore 221 | bytes 222 | Float 223 | int 224 | Decimal 225 | bool 226 | datetime 227 | date 228 | timedelta 229 | None 230 ), 231 text: str | None = None, 232 cell_type: str | None = None, 233 currency: str | None = None, 234 formula: str | None = None, 235 ) -> None: 236 """Set the cell state from the Python value type. 237 238 Text is how the cell is displayed. Cell type is guessed, 239 unless provided. 240 241 For monetary values, provide the name of the currency. 242 243 Arguments: 244 245 value -- Python type 246 247 text -- str 248 249 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 250 'currency' or 'percentage' 251 252 currency -- str 253 """ 254 self.clear() 255 text = self.set_value_and_type( 256 value=value, 257 text=text, 258 value_type=cell_type, 259 currency=currency, 260 ) 261 if text is not None: 262 self.text_content = text 263 if formula is not None: 264 self.formula = formula
Set the cell state from the Python value type.
Text is how the cell is displayed. Cell type is guessed, unless provided.
For monetary values, provide the name of the currency.
Arguments:
value -- Python type
text -- str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency' or 'percentage'
currency -- str
266 @property 267 def type(self) -> str | None: 268 """Get / set the type of the cell: boolean, float, date, string 269 or time. 270 271 Return: str | None 272 """ 273 return self.get_attribute_string("office:value-type")
Get / set the type of the cell: boolean, float, date, string or time.
Return: str | None
279 @property 280 def currency(self) -> str | None: 281 """Get / set the currency used for monetary values. 282 283 Return: str | None 284 """ 285 return self.get_attribute_string("office:currency")
Get / set the currency used for monetary values.
Return: str | None
301 @property 302 def repeated(self) -> int | None: 303 """Get / set the number of times the cell is repeated. 304 305 Always None when using the table API. 306 307 Return: int or None 308 """ 309 repeated = self.get_attribute("table:number-columns-repeated") 310 if repeated is None: 311 return None 312 return int(repeated)
Get / set the number of times the cell is repeated.
Always None when using the table API.
Return: int or None
333 @property 334 def style(self) -> str | None: 335 """Get / set the style of the cell itself. 336 337 Return: str | None 338 """ 339 return self.get_attribute_string("table:style-name")
Get / set the style of the cell itself.
Return: str | None
345 @property 346 def formula(self) -> str | None: 347 """Get / set the formula of the cell, or None if undefined. 348 349 The formula is not interpreted in any way. 350 351 Return: str | None 352 """ 353 return self.get_attribute_string("table:formula")
Get / set the formula of the cell, or None if undefined.
The formula is not interpreted in any way.
Return: str | None
359 def is_empty(self, aggressive: bool = False) -> bool: 360 if self.value is not None or self.children: 361 return False 362 if not aggressive and self.style is not None: 363 return False 364 return True
Check if the element is empty : no text, no children, no tail.
Return: Boolean
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
37class ChangeInfo(Element): 38 """The "office:change-info" element represents who made a change and when. 39 It may also contain a comment (one or more Paragrah "text:p" elements) 40 on the change. 41 42 The comments available in the ChangeInfo are available through: 43 - get_paragraphs and get_paragraph methods for actual Paragraph. 44 - get_comments for a plain text version 45 46 Arguments: 47 48 creator -- str (or None) 49 50 date -- datetime (or None) 51 """ 52 53 _tag = "office:change-info" 54 55 def __init__( 56 self, 57 creator: str | None = None, 58 date: datetime | None = None, 59 **kwargs: Any, 60 ) -> None: 61 super().__init__(**kwargs) 62 if self._do_init: 63 self.set_dc_creator(creator) 64 self.set_dc_date(date) 65 66 def set_dc_creator(self, creator: str | None = None) -> None: 67 """Set the creator of the change. Default for creator is 'Unknown'. 68 69 Arguments: 70 71 creator -- str (or None) 72 """ 73 element = self.get_element("dc:creator") 74 if element is None: 75 element = Element.from_tag("dc:creator") 76 self.insert(element, xmlposition=FIRST_CHILD) 77 if not creator: 78 creator = "Unknown" 79 element.text = creator 80 81 def set_dc_date(self, date: datetime | None = None) -> None: 82 """Set the date of the change. If date is None, use current time. 83 84 Arguments: 85 86 date -- datetime (or None) 87 """ 88 if date is None: 89 date = datetime.now() 90 dcdate = DateTime.encode(date) 91 element = self.get_element("dc:date") 92 if element is None: 93 element = Element.from_tag("dc:date") 94 self.insert(element, xmlposition=LAST_CHILD) 95 element.text = dcdate 96 97 def get_comments(self, joined: bool = True) -> str | list[str]: 98 """Get text content of the comments. If joined is True (default), the 99 text of different paragraphs is concatenated, else a list of strings, 100 one per paragraph, is returned. 101 102 Arguments: 103 104 joined -- boolean (default is True) 105 106 Return: str or list of str. 107 """ 108 content = self.get_paragraphs() 109 if content is None: 110 content = [] 111 text = [para.get_formatted_text(simple=True) for para in content] # type: ignore 112 if joined: 113 return "\n".join(text) 114 return text 115 116 def set_comments(self, text: str = "", replace: bool = True) -> None: 117 """Set the text content of the comments. If replace is True (default), 118 the new text replace old comments, else it is added at the end. 119 120 Arguments: 121 122 text -- str 123 124 replace -- boolean 125 """ 126 if replace: 127 for para in self.get_paragraphs(): 128 self.delete(para) 129 para = Paragraph() 130 para.append_plain_text(text) 131 self.insert(para, xmlposition=LAST_CHILD)
The "office:change-info" element represents who made a change and when. It may also contain a comment (one or more Paragrah "text:p" elements) on the change.
The comments available in the ChangeInfo are available through:
- get_paragraphs and get_paragraph methods for actual Paragraph.
- get_comments for a plain text version
Arguments:
creator -- str (or None)
date -- datetime (or None)
66 def set_dc_creator(self, creator: str | None = None) -> None: 67 """Set the creator of the change. Default for creator is 'Unknown'. 68 69 Arguments: 70 71 creator -- str (or None) 72 """ 73 element = self.get_element("dc:creator") 74 if element is None: 75 element = Element.from_tag("dc:creator") 76 self.insert(element, xmlposition=FIRST_CHILD) 77 if not creator: 78 creator = "Unknown" 79 element.text = creator
Set the creator of the change. Default for creator is 'Unknown'.
Arguments:
creator -- str (or None)
81 def set_dc_date(self, date: datetime | None = None) -> None: 82 """Set the date of the change. If date is None, use current time. 83 84 Arguments: 85 86 date -- datetime (or None) 87 """ 88 if date is None: 89 date = datetime.now() 90 dcdate = DateTime.encode(date) 91 element = self.get_element("dc:date") 92 if element is None: 93 element = Element.from_tag("dc:date") 94 self.insert(element, xmlposition=LAST_CHILD) 95 element.text = dcdate
Set the date of the change. If date is None, use current time.
Arguments:
date -- datetime (or None)
97 def get_comments(self, joined: bool = True) -> str | list[str]: 98 """Get text content of the comments. If joined is True (default), the 99 text of different paragraphs is concatenated, else a list of strings, 100 one per paragraph, is returned. 101 102 Arguments: 103 104 joined -- boolean (default is True) 105 106 Return: str or list of str. 107 """ 108 content = self.get_paragraphs() 109 if content is None: 110 content = [] 111 text = [para.get_formatted_text(simple=True) for para in content] # type: ignore 112 if joined: 113 return "\n".join(text) 114 return text
Get text content of the comments. If joined is True (default), the text of different paragraphs is concatenated, else a list of strings, one per paragraph, is returned.
Arguments:
joined -- boolean (default is True)
Return: str or list of str.
116 def set_comments(self, text: str = "", replace: bool = True) -> None: 117 """Set the text content of the comments. If replace is True (default), 118 the new text replace old comments, else it is added at the end. 119 120 Arguments: 121 122 text -- str 123 124 replace -- boolean 125 """ 126 if replace: 127 for para in self.get_paragraphs(): 128 self.delete(para) 129 para = Paragraph() 130 para.append_plain_text(text) 131 self.insert(para, xmlposition=LAST_CHILD)
Set the text content of the comments. If replace is True (default), the new text replace old comments, else it is added at the end.
Arguments:
text -- str
replace -- boolean
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
164class Column(Element): 165 """ODF table column "table:table-column" """ 166 167 _tag = "table:table-column" 168 _caching = True 169 170 def __init__( 171 self, 172 default_cell_style: str | None = None, 173 repeated: int | None = None, 174 style: str | None = None, 175 **kwargs: Any, 176 ) -> None: 177 """Create a column group element of the optionally given style. Cell 178 style can be set for the whole column. If the properties apply to 179 several columns, give the number of repeated columns. 180 181 Columns don't contain cells, just style information. 182 183 You don't generally have to create columns by hand, use the Table API. 184 185 Arguments: 186 187 default_cell_style -- str 188 189 repeated -- int 190 191 style -- str 192 """ 193 super().__init__(**kwargs) 194 self.x = None 195 if self._do_init: 196 if default_cell_style: 197 self.set_default_cell_style(default_cell_style) 198 if repeated and repeated > 1: 199 self.repeated = repeated 200 if style: 201 self.style = style 202 203 @property 204 def clone(self) -> Column: 205 clone = Element.clone.fget(self) # type: ignore 206 clone.x = self.x 207 if hasattr(self, "_tmap"): 208 if hasattr(self, "_rmap"): 209 clone._rmap = self._rmap[:] 210 clone._tmap = self._tmap[:] 211 clone._cmap = self._cmap[:] 212 return clone 213 214 def get_default_cell_style(self) -> str | None: 215 return self.get_attribute_string("table:default-cell-style-name") 216 217 def set_default_cell_style(self, style: Element | str) -> None: 218 self.set_style_attribute("table:default-cell-style-name", style) 219 220 def _set_repeated(self, repeated: int | None) -> None: 221 """Internal only. Set the number of times the column is repeated, or 222 None to delete it. Without changing cache. 223 224 Arguments: 225 226 repeated -- int or None 227 """ 228 if repeated is None or repeated < 2: 229 with contextlib.suppress(KeyError): 230 self.del_attribute("table:number-columns-repeated") 231 return 232 self.set_attribute("table:number-columns-repeated", str(repeated)) 233 234 @property 235 def repeated(self) -> int | None: 236 """Get /set the number of times the column is repeated. 237 238 Always None when using the table API. 239 240 Return: int or None 241 """ 242 repeated = self.get_attribute("table:number-columns-repeated") 243 if repeated is None: 244 return None 245 return int(repeated) 246 247 @repeated.setter 248 def repeated(self, repeated: int | None) -> None: 249 self._set_repeated(repeated) 250 # update cache 251 current: Element = self 252 while True: 253 # look for Table, parent may be group of rows 254 upper = current.parent 255 if not upper: 256 # lonely column 257 return 258 # parent may be group of rows, not table 259 if isinstance(upper, Table): 260 break 261 current = upper 262 # fixme : need to optimize this 263 if isinstance(upper, Table): 264 upper._compute_table_cache() 265 if hasattr(self, "_cmap"): 266 del self._cmap[:] 267 self._cmap.extend(upper._cmap) 268 else: 269 self._cmap = upper._cmap 270 271 @property 272 def style(self) -> str | None: 273 """Get /set the style of the column itself. 274 275 Return: str 276 """ 277 return self.get_attribute_string("table:style-name") 278 279 @style.setter 280 def style(self, style: str | Element) -> None: 281 self.set_style_attribute("table:style-name", style)
ODF table column "table:table-column"
170 def __init__( 171 self, 172 default_cell_style: str | None = None, 173 repeated: int | None = None, 174 style: str | None = None, 175 **kwargs: Any, 176 ) -> None: 177 """Create a column group element of the optionally given style. Cell 178 style can be set for the whole column. If the properties apply to 179 several columns, give the number of repeated columns. 180 181 Columns don't contain cells, just style information. 182 183 You don't generally have to create columns by hand, use the Table API. 184 185 Arguments: 186 187 default_cell_style -- str 188 189 repeated -- int 190 191 style -- str 192 """ 193 super().__init__(**kwargs) 194 self.x = None 195 if self._do_init: 196 if default_cell_style: 197 self.set_default_cell_style(default_cell_style) 198 if repeated and repeated > 1: 199 self.repeated = repeated 200 if style: 201 self.style = style
Create a column group element of the optionally given style. Cell style can be set for the whole column. If the properties apply to several columns, give the number of repeated columns.
Columns don't contain cells, just style information.
You don't generally have to create columns by hand, use the Table API.
Arguments:
default_cell_style -- str
repeated -- int
style -- str
234 @property 235 def repeated(self) -> int | None: 236 """Get /set the number of times the column is repeated. 237 238 Always None when using the table API. 239 240 Return: int or None 241 """ 242 repeated = self.get_attribute("table:number-columns-repeated") 243 if repeated is None: 244 return None 245 return int(repeated)
Get /set the number of times the column is repeated.
Always None when using the table API.
Return: int or None
271 @property 272 def style(self) -> str | None: 273 """Get /set the style of the column itself. 274 275 Return: str 276 """ 277 return self.get_attribute_string("table:style-name")
Get /set the style of the column itself.
Return: str
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
242class ConnectorShape(ShapeBase): 243 """Create a Connector shape. 244 245 Arguments: 246 247 style -- str 248 249 text_style -- str 250 251 draw_id -- str 252 253 layer -- str 254 255 connected_shapes -- (shape, shape) 256 257 glue_points -- (point, point) 258 259 p1 -- (str, str) 260 261 p2 -- (str, str) 262 """ 263 264 _tag = "draw:connector" 265 _properties: tuple[PropDef, ...] = ( 266 PropDef("start_shape", "draw:start-shape"), 267 PropDef("end_shape", "draw:end-shape"), 268 PropDef("start_glue_point", "draw:start-glue-point"), 269 PropDef("end_glue_point", "draw:end-glue-point"), 270 PropDef("x1", "svg:x1"), 271 PropDef("y1", "svg:y1"), 272 PropDef("x2", "svg:x2"), 273 PropDef("y2", "svg:y2"), 274 ) 275 276 def __init__( 277 self, 278 style: str | None = None, 279 text_style: str | None = None, 280 draw_id: str | None = None, 281 layer: str | None = None, 282 connected_shapes: tuple | None = None, 283 glue_points: tuple | None = None, 284 p1: tuple | None = None, 285 p2: tuple | None = None, 286 **kwargs: Any, 287 ) -> None: 288 kwargs.update( 289 { 290 "style": style, 291 "text_style": text_style, 292 "draw_id": draw_id, 293 "layer": layer, 294 } 295 ) 296 super().__init__(**kwargs) 297 if self._do_init: 298 if connected_shapes: 299 self.start_shape = connected_shapes[0].draw_id 300 self.end_shape = connected_shapes[1].draw_id 301 if glue_points: 302 self.start_glue_point = glue_points[0] 303 self.end_glue_point = glue_points[1] 304 if p1: 305 self.x1 = p1[0] 306 self.y1 = p1[1] 307 if p2: 308 self.x2 = p2[0] 309 self.y2 = p2[1]
Create a Connector shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
connected_shapes -- (shape, shape)
glue_points -- (point, point)
p1 -- (str, str)
p2 -- (str, str)
276 def __init__( 277 self, 278 style: str | None = None, 279 text_style: str | None = None, 280 draw_id: str | None = None, 281 layer: str | None = None, 282 connected_shapes: tuple | None = None, 283 glue_points: tuple | None = None, 284 p1: tuple | None = None, 285 p2: tuple | None = None, 286 **kwargs: Any, 287 ) -> None: 288 kwargs.update( 289 { 290 "style": style, 291 "text_style": text_style, 292 "draw_id": draw_id, 293 "layer": layer, 294 } 295 ) 296 super().__init__(**kwargs) 297 if self._do_init: 298 if connected_shapes: 299 self.start_shape = connected_shapes[0].draw_id 300 self.end_shape = connected_shapes[1].draw_id 301 if glue_points: 302 self.start_glue_point = glue_points[0] 303 self.end_glue_point = glue_points[1] 304 if p1: 305 self.x1 = p1[0] 306 self.y1 = p1[1] 307 if p2: 308 self.x2 = p2[0] 309 self.y2 = p2[1]
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
55class Container: 56 """Representation of the ODF file.""" 57 58 def __init__(self, path: Path | str | io.BytesIO | None = None) -> None: 59 self.__parts: dict[str, bytes | None] = {} 60 self.__parts_ts: dict[str, int] = {} 61 self.__path_like: Path | str | io.BytesIO | None = None 62 self.__packaging: str = "zip" 63 self.path: Path | None = None # or Path 64 if path: 65 self.open(path) 66 67 def __repr__(self) -> str: 68 return f"<{self.__class__.__name__} type={self.mimetype} path={self.path}>" 69 70 def open(self, path_or_file: Path | str | io.BytesIO) -> None: 71 """Load the content of an ODF file.""" 72 self.__path_like = path_or_file 73 if isinstance(path_or_file, (str, Path)): 74 self.path = Path(path_or_file) 75 if not self.path.exists(): 76 raise FileNotFoundError(str(self.path)) 77 if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile( 78 path_or_file 79 ): 80 self.__packaging = "zip" 81 return self._read_zip() 82 if self.path: 83 is_folder = False 84 with contextlib.suppress(OSError): 85 is_folder = self.path.is_dir() 86 if is_folder: 87 self.__packaging = "folder" 88 return self._read_folder() 89 raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.") 90 91 def _read_zip(self) -> None: 92 if isinstance(self.__path_like, io.BytesIO): 93 self.__path_like.seek(0) 94 with ZipFile(self.__path_like) as zf: # type: ignore 95 mimetype = bytes_to_str(zf.read("mimetype")) 96 if mimetype not in ODF_MIMETYPES: 97 raise ValueError(f"Document of unknown type {mimetype}") 98 self.__parts["mimetype"] = str_to_bytes(mimetype) 99 if self.path is None: 100 if isinstance(self.__path_like, io.BytesIO): 101 self.__path_like.seek(0) 102 # read the full file at once and forget file 103 with ZipFile(self.__path_like) as zf: # type: ignore 104 for name in zf.namelist(): 105 upath = normalize_path(name) 106 self.__parts[upath] = zf.read(name) 107 self.__path_like = None 108 109 def _read_folder(self) -> None: 110 try: 111 mimetype, timestamp = self._get_folder_part("mimetype") 112 except OSError: 113 printwarn("Corrupted or not an OpenDocument folder (missing mimetype)") 114 mimetype = b"" 115 timestamp = int(time.time()) 116 if bytes_to_str(mimetype) not in ODF_MIMETYPES: 117 message = f"Document of unknown type {mimetype!r}, try with ODF Text." 118 printwarn(message) 119 self.__parts["mimetype"] = str_to_bytes(ODF_EXTENSIONS["odt"]) 120 self.__parts_ts["mimetype"] = timestamp 121 122 def _parse_folder(self, folder: str) -> list[str]: 123 parts = [] 124 if self.path is None: 125 raise ValueError("Document path is not defined") 126 root = self.path / folder 127 for path in root.iterdir(): 128 if path.name.startswith("."): # no hidden files 129 continue 130 relative_path = path.relative_to(self.path) 131 if path.is_file(): 132 parts.append(relative_path.as_posix()) 133 if path.is_dir(): 134 sub_parts = self._parse_folder(str(relative_path)) 135 if sub_parts: 136 parts.extend(sub_parts) 137 else: 138 # store leaf directories 139 parts.append(relative_path.as_posix() + "/") 140 return parts 141 142 def _get_folder_parts(self) -> list[str]: 143 """Get the list of members in the ODF folder.""" 144 return self._parse_folder("") 145 146 def _get_folder_part(self, name: str) -> tuple[bytes, int]: 147 """Get bytes of a part from the ODF folder, with timestamp.""" 148 if self.path is None: 149 raise ValueError("Document path is not defined") 150 path = self.path / name 151 timestamp = int(path.stat().st_mtime) 152 if path.is_dir(): 153 return (b"", timestamp) 154 return (path.read_bytes(), timestamp) 155 156 def _get_folder_part_timestamp(self, name: str) -> int: 157 if self.path is None: 158 raise ValueError("Document path is not defined") 159 path = self.path / name 160 try: 161 timestamp = int(path.stat().st_mtime) 162 except OSError: 163 timestamp = -1 164 return timestamp 165 166 def _get_zip_part(self, name: str) -> bytes | None: 167 """Get bytes of a part from the Zip ODF file. No cache.""" 168 if self.path is None: 169 raise ValueError("Document path is not defined") 170 try: 171 with ZipFile(self.path) as zf: 172 upath = normalize_path(name) 173 self.__parts[upath] = zf.read(name) 174 return self.__parts[upath] 175 except BadZipfile: 176 return None 177 178 def _get_all_zip_part(self) -> None: 179 """Read all parts. No cache.""" 180 if self.path is None: 181 raise ValueError("Document path is not defined") 182 try: 183 with ZipFile(self.path) as zf: 184 for name in zf.namelist(): 185 upath = normalize_path(name) 186 self.__parts[upath] = zf.read(name) 187 except BadZipfile: 188 pass 189 190 def _save_zip(self, target: str | Path | io.BytesIO) -> None: 191 """Save a Zip ODF from the available parts.""" 192 parts = self.__parts 193 with ZipFile(target, "w", compression=ZIP_DEFLATED) as filezip: 194 # Parts to save, except manifest at the end 195 part_names = list(parts.keys()) 196 try: 197 part_names.remove(ODF_MANIFEST) 198 except ValueError: 199 printwarn(f"Missing '{ODF_MANIFEST}'") 200 # "Pretty-save" parts in some order 201 # mimetype requires to be first and uncompressed 202 mimetype = parts.get("mimetype") 203 if mimetype is None: 204 raise ValueError("Mimetype is not defined") 205 try: 206 filezip.writestr("mimetype", mimetype, ZIP_STORED) 207 part_names.remove("mimetype") 208 except (ValueError, KeyError): 209 printwarn("Missing 'mimetype'") 210 # XML parts 211 for path in ODF_CONTENT, ODF_META, ODF_SETTINGS, ODF_STYLES: 212 if path not in parts: 213 printwarn(f"Missing '{path}'") 214 continue 215 part = parts[path] 216 if part is None: 217 continue 218 filezip.writestr(path, part) 219 part_names.remove(path) 220 # Everything else 221 for path in part_names: 222 data = parts[path] 223 if data is None: 224 # Deleted 225 continue 226 filezip.writestr(path, data) 227 # Manifest 228 with contextlib.suppress(KeyError): 229 part = parts[ODF_MANIFEST] 230 if part is not None: 231 filezip.writestr(ODF_MANIFEST, part) 232 233 def _save_folder(self, folder: Path | str) -> None: 234 """Save a folder ODF from the available parts.""" 235 236 def dump(part_path: str, content: bytes) -> None: 237 if part_path.endswith("/"): # folder 238 is_folder = True 239 pure_path = PurePath(folder, part_path[:-1]) 240 else: 241 is_folder = False 242 pure_path = PurePath(folder, part_path) 243 path = Path(pure_path) 244 if is_folder: 245 path.mkdir(parents=True, exist_ok=True) 246 else: 247 path.parent.mkdir(parents=True, exist_ok=True) 248 path.write_bytes(content) 249 path.chmod(0o666) 250 251 for part_path, data in self.__parts.items(): 252 if data is None: 253 # Deleted 254 continue 255 dump(part_path, data) 256 257 # Public API 258 259 def get_parts(self) -> list[str]: 260 """Get the list of members.""" 261 if not self.path: 262 # maybe a file like zip archive 263 return list(self.__parts.keys()) 264 if self.__packaging == "zip": 265 parts = [] 266 with ZipFile(self.path) as zf: 267 for name in zf.namelist(): 268 upath = normalize_path(name) 269 parts.append(upath) 270 return parts 271 elif self.__packaging == "folder": 272 return self._get_folder_parts() 273 else: 274 raise ValueError("Unable to provide parts of the document") 275 276 def get_part(self, path: str) -> str | bytes | None: 277 """Get the bytes of a part of the ODF.""" 278 path = str(path) 279 if path in self.__parts: 280 part = self.__parts[path] 281 if part is None: 282 raise ValueError(f'Part "{path}" is deleted') 283 if self.__packaging == "folder": 284 cache_ts = self.__parts_ts.get(path, -1) 285 current_ts = self._get_folder_part_timestamp(path) 286 if current_ts != cache_ts: 287 part, timestamp = self._get_folder_part(path) 288 self.__parts[path] = part 289 self.__parts_ts[path] = timestamp 290 return part 291 if self.__packaging == "zip": 292 return self._get_zip_part(path) 293 if self.__packaging == "folder": 294 part, timestamp = self._get_folder_part(path) 295 self.__parts[path] = part 296 self.__parts_ts[path] = timestamp 297 return part 298 return None 299 300 @property 301 def mimetype(self) -> str: 302 """Return str value of mimetype of the document.""" 303 with contextlib.suppress(Exception): 304 b_mimetype = self.get_part("mimetype") 305 if isinstance(b_mimetype, bytes): 306 return bytes_to_str(b_mimetype) 307 return "" 308 309 @mimetype.setter 310 def mimetype(self, mimetype: str | bytes) -> None: 311 """Set mimetype value of the document.""" 312 if isinstance(mimetype, str): 313 self.__parts["mimetype"] = str_to_bytes(mimetype) 314 elif isinstance(mimetype, bytes): 315 self.__parts["mimetype"] = mimetype 316 else: 317 raise TypeError(f'Wrong mimetype "{mimetype!r}"') 318 319 def set_part(self, path: str, data: bytes) -> None: 320 """Replace or add a new part.""" 321 self.__parts[path] = data 322 323 def del_part(self, path: str) -> None: 324 """Mark a part for deletion.""" 325 self.__parts[path] = None 326 327 @property 328 def clone(self) -> Container: 329 """Make a copy of this container with no path.""" 330 if self.path and self.__packaging == "zip": 331 self._get_all_zip_part() 332 clone = deepcopy(self) 333 clone.path = None 334 return clone 335 336 @staticmethod 337 def _do_backup(target: str | Path) -> None: 338 path = Path(target) 339 if not path.exists(): 340 return 341 back_file = Path(path.stem + ".backup" + path.suffix) 342 if back_file.is_dir(): 343 try: 344 shutil.rmtree(back_file) 345 except OSError as e: 346 printwarn(str(e)) 347 try: 348 shutil.move(target, back_file) 349 except OSError as e: 350 printwarn(str(e)) 351 352 def _save_packaging(self, packaging: str | None) -> str: 353 if not packaging: 354 packaging = self.__packaging if self.__packaging else "zip" 355 packaging = packaging.strip().lower() 356 # if packaging not in ('zip', 'flat', 'folder'): 357 if packaging not in ("zip", "folder"): 358 raise ValueError(f'Packaging of type "{packaging}" is not supported') 359 return packaging 360 361 def _save_target(self, target: str | Path | io.BytesIO | None) -> str | io.BytesIO: 362 if target is None: 363 target = self.path 364 if isinstance(target, Path): 365 target = str(target) 366 if isinstance(target, str): 367 while target.endswith(os.sep): 368 target = target[:-1] 369 while target.endswith(".folder"): 370 target = target.split(".folder", 1)[0] 371 return target # type: ignore 372 373 def _save_as_zip(self, target: str | Path | io.BytesIO, backup: bool) -> None: 374 if isinstance(target, (str, Path)) and backup: 375 self._do_backup(target) 376 self._save_zip(target) 377 378 def _save_as_folder(self, target: str | Path, backup: bool) -> None: 379 if not isinstance(target, (str, Path)): 380 raise TypeError( 381 f"Saving in folder format requires a folder name, not '{target!r}'" 382 ) 383 if not str(target).endswith(".folder"): 384 target = str(target) + ".folder" 385 if backup: 386 self._do_backup(target) 387 else: 388 path = Path(target) 389 if path.exists(): 390 try: 391 shutil.rmtree(path) 392 except OSError as e: 393 printwarn(str(e)) 394 self._save_folder(target) 395 396 def save( 397 self, 398 target: str | Path | io.BytesIO | None, 399 packaging: str | None = None, 400 backup: bool = False, 401 ) -> None: 402 """Save the container to the given target, a path or a file-like 403 object. 404 405 Package the output document in the same format than current document, 406 unless "packaging" is different. 407 408 Arguments: 409 410 target -- str or file-like or Path 411 412 packaging -- 'zip', or for debugging purpose 'folder' 413 414 backup -- boolean 415 """ 416 parts = self.__parts 417 packaging = self._save_packaging(packaging) 418 # Load parts else they will be considered deleted 419 for path in self.get_parts(): 420 if path not in parts: 421 self.get_part(path) 422 target = self._save_target(target) 423 if packaging == "folder": 424 if isinstance(target, io.BytesIO): 425 raise TypeError( 426 "Impossible to save on io.BytesIO with 'folder' packaging" 427 ) 428 self._save_as_folder(target, backup) 429 else: 430 # default: 431 self._save_as_zip(target, backup)
Representation of the ODF file.
58 def __init__(self, path: Path | str | io.BytesIO | None = None) -> None: 59 self.__parts: dict[str, bytes | None] = {} 60 self.__parts_ts: dict[str, int] = {} 61 self.__path_like: Path | str | io.BytesIO | None = None 62 self.__packaging: str = "zip" 63 self.path: Path | None = None # or Path 64 if path: 65 self.open(path)
70 def open(self, path_or_file: Path | str | io.BytesIO) -> None: 71 """Load the content of an ODF file.""" 72 self.__path_like = path_or_file 73 if isinstance(path_or_file, (str, Path)): 74 self.path = Path(path_or_file) 75 if not self.path.exists(): 76 raise FileNotFoundError(str(self.path)) 77 if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile( 78 path_or_file 79 ): 80 self.__packaging = "zip" 81 return self._read_zip() 82 if self.path: 83 is_folder = False 84 with contextlib.suppress(OSError): 85 is_folder = self.path.is_dir() 86 if is_folder: 87 self.__packaging = "folder" 88 return self._read_folder() 89 raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")
Load the content of an ODF file.
259 def get_parts(self) -> list[str]: 260 """Get the list of members.""" 261 if not self.path: 262 # maybe a file like zip archive 263 return list(self.__parts.keys()) 264 if self.__packaging == "zip": 265 parts = [] 266 with ZipFile(self.path) as zf: 267 for name in zf.namelist(): 268 upath = normalize_path(name) 269 parts.append(upath) 270 return parts 271 elif self.__packaging == "folder": 272 return self._get_folder_parts() 273 else: 274 raise ValueError("Unable to provide parts of the document")
Get the list of members.
276 def get_part(self, path: str) -> str | bytes | None: 277 """Get the bytes of a part of the ODF.""" 278 path = str(path) 279 if path in self.__parts: 280 part = self.__parts[path] 281 if part is None: 282 raise ValueError(f'Part "{path}" is deleted') 283 if self.__packaging == "folder": 284 cache_ts = self.__parts_ts.get(path, -1) 285 current_ts = self._get_folder_part_timestamp(path) 286 if current_ts != cache_ts: 287 part, timestamp = self._get_folder_part(path) 288 self.__parts[path] = part 289 self.__parts_ts[path] = timestamp 290 return part 291 if self.__packaging == "zip": 292 return self._get_zip_part(path) 293 if self.__packaging == "folder": 294 part, timestamp = self._get_folder_part(path) 295 self.__parts[path] = part 296 self.__parts_ts[path] = timestamp 297 return part 298 return None
Get the bytes of a part of the ODF.
300 @property 301 def mimetype(self) -> str: 302 """Return str value of mimetype of the document.""" 303 with contextlib.suppress(Exception): 304 b_mimetype = self.get_part("mimetype") 305 if isinstance(b_mimetype, bytes): 306 return bytes_to_str(b_mimetype) 307 return ""
Return str value of mimetype of the document.
319 def set_part(self, path: str, data: bytes) -> None: 320 """Replace or add a new part.""" 321 self.__parts[path] = data
Replace or add a new part.
323 def del_part(self, path: str) -> None: 324 """Mark a part for deletion.""" 325 self.__parts[path] = None
Mark a part for deletion.
327 @property 328 def clone(self) -> Container: 329 """Make a copy of this container with no path.""" 330 if self.path and self.__packaging == "zip": 331 self._get_all_zip_part() 332 clone = deepcopy(self) 333 clone.path = None 334 return clone
Make a copy of this container with no path.
396 def save( 397 self, 398 target: str | Path | io.BytesIO | None, 399 packaging: str | None = None, 400 backup: bool = False, 401 ) -> None: 402 """Save the container to the given target, a path or a file-like 403 object. 404 405 Package the output document in the same format than current document, 406 unless "packaging" is different. 407 408 Arguments: 409 410 target -- str or file-like or Path 411 412 packaging -- 'zip', or for debugging purpose 'folder' 413 414 backup -- boolean 415 """ 416 parts = self.__parts 417 packaging = self._save_packaging(packaging) 418 # Load parts else they will be considered deleted 419 for path in self.get_parts(): 420 if path not in parts: 421 self.get_part(path) 422 target = self._save_target(target) 423 if packaging == "folder": 424 if isinstance(target, io.BytesIO): 425 raise TypeError( 426 "Impossible to save on io.BytesIO with 'folder' packaging" 427 ) 428 self._save_as_folder(target, backup) 429 else: 430 # default: 431 self._save_as_zip(target, backup)
Save the container to the given target, a path or a file-like object.
Package the output document in the same format than current document, unless "packaging" is different.
Arguments:
target -- str or file-like or Path
packaging -- 'zip', or for debugging purpose 'folder'
backup -- boolean
34class Content(XmlPart): 35 @property 36 def body(self) -> Element: 37 body = self.root.document_body 38 if not isinstance(body, Element): 39 raise ValueError("No body found in document") # noqa:TRY004 40 return body 41 42 # The following two seem useless but they match styles API 43 44 def _get_style_contexts(self, family: str | None) -> tuple: 45 if family == "font-face": 46 return (self.get_element("//office:font-face-decls"),) 47 return ( 48 self.get_element("//office:font-face-decls"), 49 self.get_element("//office:automatic-styles"), 50 ) 51 52 def __str__(self) -> str: 53 return str(self.body) 54 55 # Public API 56 57 def get_styles(self, family: str | None = None) -> list[Style]: 58 """Return the list of styles in the Content part, optionally limited 59 to the given family. 60 61 Arguments: 62 63 family -- str or None 64 65 Return: list of Style 66 """ 67 result: list[Style] = [] 68 for context in self._get_style_contexts(family): 69 if context is None: 70 continue 71 result.extend(context.get_styles(family=family)) 72 return result 73 74 def get_style( 75 self, 76 family: str, 77 name_or_element: str | Element | None = None, 78 display_name: str | None = None, 79 ) -> Style | None: 80 """Return the style uniquely identified by the name/family pair. If 81 the argument is already a style object, it will return it. 82 83 If the name is None, the default style is fetched. 84 85 If the name is not the internal name but the name you gave in the 86 desktop application, use display_name instead. 87 88 Arguments: 89 90 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 91 'number' 92 93 name_or_element -- str or Style 94 95 display_name -- str 96 97 Return: Style or None if not found 98 """ 99 for context in self._get_style_contexts(family): 100 if context is None: 101 continue 102 style = context.get_style( 103 family, 104 name_or_element=name_or_element, 105 display_name=display_name, 106 ) 107 if style is not None: 108 return style 109 return None
Representation of an XML part.
Abstraction of the XML library behind.
57 def get_styles(self, family: str | None = None) -> list[Style]: 58 """Return the list of styles in the Content part, optionally limited 59 to the given family. 60 61 Arguments: 62 63 family -- str or None 64 65 Return: list of Style 66 """ 67 result: list[Style] = [] 68 for context in self._get_style_contexts(family): 69 if context is None: 70 continue 71 result.extend(context.get_styles(family=family)) 72 return result
Return the list of styles in the Content part, optionally limited to the given family.
Arguments:
family -- str or None
Return: list of Style
74 def get_style( 75 self, 76 family: str, 77 name_or_element: str | Element | None = None, 78 display_name: str | None = None, 79 ) -> Style | None: 80 """Return the style uniquely identified by the name/family pair. If 81 the argument is already a style object, it will return it. 82 83 If the name is None, the default style is fetched. 84 85 If the name is not the internal name but the name you gave in the 86 desktop application, use display_name instead. 87 88 Arguments: 89 90 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 91 'number' 92 93 name_or_element -- str or Style 94 95 display_name -- str 96 97 Return: Style or None if not found 98 """ 99 for context in self._get_style_contexts(family): 100 if context is None: 101 continue 102 style = context.get_style( 103 family, 104 name_or_element=name_or_element, 105 display_name=display_name, 106 ) 107 if style is not None: 108 return style 109 return None
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number'
name_or_element -- str or Style
display_name -- str
Return: Style or None if not found
Inherited Members
155class Document: 156 """Abstraction of the ODF document. 157 158 To create a new Document, several possibilities: 159 160 - Document() or Document("text") -> an "empty" document of type text 161 - Document("spreadsheet") -> an "empty" document of type spreadsheet 162 - Document("presentation") -> an "empty" document of type presentation 163 - Document("drawing") -> an "empty" document of type drawing 164 165 Meaning of “empty”: these documents are copies of the default 166 templates documents provided with this library, which, as templates, 167 are not really empty. It may be useful to clear the newly created 168 document: document.body.clear(), or adjust meta informations like 169 description or default language: document.meta.set_language('fr-FR') 170 171 If the argument is not a known template type, or is a Path, 172 Document(file) will load the content of the ODF file. 173 174 To explicitly create a document from a custom template, use the 175 Document.new(path) method whose argument is the path to the template file. 176 """ 177 178 def __init__( 179 self, 180 target: str | bytes | Path | Container | io.BytesIO | None = "text", 181 ) -> None: 182 # Cache of XML parts 183 self.__xmlparts: dict[str, XmlPart] = {} 184 # Cache of the body 185 self.__body: Element | None = None 186 self.container: Container | None = None 187 if isinstance(target, bytes): 188 # eager conversion 189 target = bytes_to_str(target) 190 if target is None: 191 # empty document, you probably don't wnat this. 192 self.container = Container() 193 return 194 if isinstance(target, Path): 195 # let's assume we open a container on existing file 196 self.container = Container(target) 197 return 198 if isinstance(target, Container): 199 # special internal case, use an existing container 200 self.container = target 201 return 202 if isinstance(target, str): 203 if target in ODF_TEMPLATES: 204 # assuming a new document from templates 205 self.container = container_from_template(target) 206 return 207 # let's assume we open a container on existing file 208 self.container = Container(target) 209 return 210 if isinstance(target, io.BytesIO): 211 self.container = Container(target) 212 return 213 raise TypeError(f"Unknown Document source type: '{target!r}'") 214 215 def __repr__(self) -> str: 216 return f"<{self.__class__.__name__} type={self.get_type()} path={self.path}>" 217 218 def __str__(self) -> str: 219 try: 220 return str(self.get_formatted_text()) 221 except NotImplementedError: 222 return self.body.text_recursive 223 224 @classmethod 225 def new(cls, template: str | Path | io.BytesIO = "text") -> Document: 226 """Create a Document from a template. 227 228 The template argument is expected to be the path to a ODF template. 229 230 Arguments: 231 232 template -- str or Path or file-like (io.BytesIO) 233 234 Return : ODF document -- Document 235 """ 236 container = container_from_template(template) 237 return cls(container) 238 239 # Public API 240 241 @property 242 def path(self) -> Path | None: 243 """Shortcut to Document.Container.path.""" 244 if not self.container: 245 return None 246 return self.container.path 247 248 @path.setter 249 def path(self, path_or_str: str | Path) -> None: 250 """Shortcut to Document.Container.path 251 252 Only accepting str or Path.""" 253 if not self.container: 254 return 255 self.container.path = Path(path_or_str) 256 257 def get_parts(self) -> list[str]: 258 """Return available part names with path inside the archive, e.g. 259 ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg'] 260 """ 261 if not self.container: 262 raise ValueError("Empty Container") 263 return self.container.get_parts() 264 265 def get_part(self, path: str) -> XmlPart | str | bytes | None: 266 """Return the bytes of the given part. The path is relative to the 267 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 268 269 'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts 270 to the real path, e.g. content.xml, and return a dedicated object with 271 its own API. 272 273 path formated as URI, so always use '/' separator 274 """ 275 if not self.container: 276 raise ValueError("Empty Container") 277 # "./ObjectReplacements/Object 1" 278 path = path.lstrip("./") 279 path = _get_part_path(path) 280 cls = _get_part_class(path) 281 # Raw bytes 282 if cls is None: 283 return self.container.get_part(path) 284 # XML part 285 part = self.__xmlparts.get(path) 286 if part is None: 287 self.__xmlparts[path] = part = cls(path, self.container) 288 return part 289 290 def set_part(self, path: str, data: bytes) -> None: 291 """Set the bytes of the given part. The path is relative to the 292 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 293 294 path formated as URI, so always use '/' separator 295 """ 296 if not self.container: 297 raise ValueError("Empty Container") 298 # "./ObjectReplacements/Object 1" 299 path = path.lstrip("./") 300 path = _get_part_path(path) 301 cls = _get_part_class(path) 302 # XML part overwritten 303 if cls is not None: 304 with suppress(KeyError): 305 self.__xmlparts[path] 306 self.container.set_part(path, data) 307 308 def del_part(self, path: str) -> None: 309 """Mark a part for deletion. The path is relative to the archive, 310 e.g. "Pictures/1003200258912EB1C3.jpg" 311 """ 312 if not self.container: 313 raise ValueError("Empty Container") 314 path = _get_part_path(path) 315 cls = _get_part_class(path) 316 if path == ODF_MANIFEST or cls is not None: 317 raise ValueError(f"part '{path}' is mandatory") 318 self.container.del_part(path) 319 320 @property 321 def mimetype(self) -> str: 322 if not self.container: 323 raise ValueError("Empty Container") 324 return self.container.mimetype 325 326 @mimetype.setter 327 def mimetype(self, mimetype: str) -> None: 328 if not self.container: 329 raise ValueError("Empty Container") 330 self.container.mimetype = mimetype 331 332 def get_type(self) -> str: 333 """Get the ODF type (also called class) of this document. 334 335 Return: 'chart', 'database', 'formula', 'graphics', 336 'graphics-template', 'image', 'presentation', 337 'presentation-template', 'spreadsheet', 'spreadsheet-template', 338 'text', 'text-master', 'text-template' or 'text-web' 339 """ 340 # The mimetype must be with the form: 341 # application/vnd.oasis.opendocument.text 342 343 # Isolate and return the last part 344 return self.mimetype.rsplit(".", 1)[-1] 345 346 @property 347 def body(self) -> Element: 348 """Return the body element of the content part, where actual content 349 is stored. 350 """ 351 if self.__body is None: 352 self.__body = self.content.body 353 return self.__body 354 355 @property 356 def meta(self) -> Meta: 357 """Return the meta part (meta.xml) of the document, where meta data 358 are stored.""" 359 metadata = self.get_part(ODF_META) 360 if metadata is None or not isinstance(metadata, Meta): 361 raise ValueError("Empty Meta") 362 return metadata 363 364 @property 365 def manifest(self) -> Manifest: 366 """Return the manifest part (manifest.xml) of the document.""" 367 manifest = self.get_part(ODF_MANIFEST) 368 if manifest is None or not isinstance(manifest, Manifest): 369 raise ValueError("Empty Manifest") 370 return manifest 371 372 def _get_formatted_text_footnotes( 373 self, 374 result: list[str], 375 context: dict, 376 rst_mode: bool, 377 ) -> None: 378 # Separate text from notes 379 if rst_mode: 380 result.append("\n") 381 else: 382 result.append("----\n") 383 for citation, body in context["footnotes"]: 384 if rst_mode: 385 result.append(f".. [#] {body}\n") 386 else: 387 result.append(f"[{citation}] {body}\n") 388 # Append a \n after the notes 389 result.append("\n") 390 # Reset for the next paragraph 391 context["footnotes"] = [] 392 393 def _get_formatted_text_annotations( 394 self, 395 result: list[str], 396 context: dict, 397 rst_mode: bool, 398 ) -> None: 399 # Insert the annotations 400 # With a separation 401 if rst_mode: 402 result.append("\n") 403 else: 404 result.append("----\n") 405 for annotation in context["annotations"]: 406 if rst_mode: 407 result.append(f".. [#] {annotation}\n") 408 else: 409 result.append(f"[*] {annotation}\n") 410 context["annotations"] = [] 411 412 def _get_formatted_text_images( 413 self, 414 result: list[str], 415 context: dict, 416 rst_mode: bool, 417 ) -> None: 418 # Insert the images ref, only in rst mode 419 result.append("\n") 420 for ref, filename, (width, height) in context["images"]: 421 result.append(f".. {ref} image:: {filename}\n") 422 if width is not None: 423 result.append(f" :width: {width}\n") 424 if height is not None: 425 result.append(f" :height: {height}\n") 426 context["images"] = [] 427 428 def _get_formatted_text_endnotes( 429 self, 430 result: list[str], 431 context: dict, 432 rst_mode: bool, 433 ) -> None: 434 # Append the end notes 435 if rst_mode: 436 result.append("\n\n") 437 else: 438 result.append("\n========\n") 439 for citation, body in context["endnotes"]: 440 if rst_mode: 441 result.append(f".. [*] {body}\n") 442 else: 443 result.append(f"({citation}) {body}\n") 444 445 def get_formatted_text(self, rst_mode: bool = False) -> str: 446 """Return content as text, with some formatting.""" 447 # For the moment, only "type='text'" 448 doc_type = self.get_type() 449 if doc_type == "spreadsheet": 450 return self._tables_csv() 451 if doc_type in { 452 "text", 453 "text-template", 454 "presentation", 455 "presentation-template", 456 }: 457 return self._formatted_text(rst_mode) 458 raise NotImplementedError(f"Type of document '{doc_type}' not supported yet") 459 460 def _tables_csv(self) -> str: 461 return "\n\n".join(str(table) for table in self.body.get_tables()) 462 463 def _formatted_text(self, rst_mode: bool) -> str: 464 # Initialize an empty context 465 context = { 466 "document": self, 467 "footnotes": [], 468 "endnotes": [], 469 "annotations": [], 470 "rst_mode": rst_mode, 471 "img_counter": 0, 472 "images": [], 473 "no_img_level": 0, 474 } 475 body = self.body 476 # Get the text 477 result = [] 478 for child in body.children: 479 # self._get_formatted_text_child(result, element, context, rst_mode) 480 # if child.tag == "table:table": 481 # result.append(child.get_formatted_text(context)) 482 # return 483 result.append(child.get_formatted_text(context)) 484 if context["footnotes"]: 485 self._get_formatted_text_footnotes(result, context, rst_mode) 486 if context["annotations"]: 487 self._get_formatted_text_annotations(result, context, rst_mode) 488 # Insert the images ref, only in rst mode 489 if context["images"]: 490 self._get_formatted_text_images(result, context, rst_mode) 491 if context["endnotes"]: 492 self._get_formatted_text_endnotes(result, context, rst_mode) 493 return "".join(result) 494 495 def get_formated_meta(self) -> str: 496 """Return meta informations as text, with some formatting.""" 497 result: list[str] = [] 498 499 # Simple values 500 def print_info(name: str, value: Any) -> None: 501 if value: 502 result.append(f"{name}: {value}") 503 504 meta = self.meta 505 print_info("Title", meta.get_title()) 506 print_info("Subject", meta.get_subject()) 507 print_info("Language", meta.get_language()) 508 print_info("Modification date", meta.get_modification_date()) 509 print_info("Creation date", meta.get_creation_date()) 510 print_info("Initial creator", meta.get_initial_creator()) 511 print_info("Keyword", meta.get_keywords()) 512 print_info("Editing duration", meta.get_editing_duration()) 513 print_info("Editing cycles", meta.get_editing_cycles()) 514 print_info("Generator", meta.get_generator()) 515 516 # Statistic 517 result.append("Statistic:") 518 statistic = meta.get_statistic() 519 if statistic: 520 for name, data in statistic.items(): 521 result.append(f" - {name[5:].replace('-', ' ').capitalize()}: {data}") 522 523 # User defined metadata 524 result.append("User defined metadata:") 525 user_metadata = meta.get_user_defined_metadata() 526 for name, data2 in user_metadata.items(): 527 result.append(f" - {name}: {data2}") 528 529 # And the description 530 print_info("Description", meta.get_description()) 531 532 return "\n".join(result) + "\n" 533 534 def add_file(self, path_or_file: str | Path) -> str: 535 """Insert a file from a path or a file-like object in the container. 536 537 Return the full path to reference in the content. 538 539 Arguments: 540 541 path_or_file -- str or Path or file-like 542 543 Return: str (URI) 544 """ 545 if not self.container: 546 raise ValueError("Empty Container") 547 name = "" 548 # Folder for added files (FIXME hard-coded and copied) 549 manifest = self.manifest 550 medias = manifest.get_paths() 551 # uuid = str(uuid4()) 552 553 if isinstance(path_or_file, (str, Path)): 554 path = Path(path_or_file) 555 extension = path.suffix.lower() 556 name = f"{path.stem}{extension}" 557 if posixpath.join("Pictures", name) in medias: 558 name = f"{path.stem}_{uuid4()}{extension}" 559 else: 560 path = None 561 name = getattr(path_or_file, "name", None) 562 if not name: 563 name = str(uuid4()) 564 media_type, _encoding = guess_type(name) 565 if not media_type: 566 media_type = "application/octet-stream" 567 if manifest.get_media_type("Pictures/") is None: 568 manifest.add_full_path("Pictures/") 569 full_path = posixpath.join("Pictures", name) 570 if path is None: 571 self.container.set_part(full_path, path_or_file.read()) 572 else: 573 self.container.set_part(full_path, path.read_bytes()) 574 manifest.add_full_path(full_path, media_type) 575 return full_path 576 577 @property 578 def clone(self) -> Document: 579 """Return an exact copy of the document. 580 581 Return: Document 582 """ 583 clone = object.__new__(self.__class__) 584 for name in self.__dict__: 585 if name == "_Document__body": 586 setattr(clone, name, None) 587 elif name == "_Document__xmlparts": 588 setattr(clone, name, {}) 589 elif name == "container": 590 if not self.container: 591 raise ValueError("Empty Container") 592 setattr(clone, name, self.container.clone) 593 else: 594 value = deepcopy(getattr(self, name)) 595 setattr(clone, name, value) 596 return clone 597 598 def save( 599 self, 600 target: str | Path | io.BytesIO | None = None, 601 packaging: str = "zip", 602 pretty: bool = False, 603 backup: bool = False, 604 ) -> None: 605 """Save the document, at the same place it was opened or at the given 606 target path. Target can also be a file-like object. It can be saved 607 as a Zip file (default) or as files in a folder (for debugging 608 purpose). XML parts can be pretty printed. 609 610 Arguments: 611 612 target -- str or file-like object 613 614 packaging -- 'zip' or 'folder' 615 616 pretty -- bool 617 618 backup -- bool 619 """ 620 if not self.container: 621 raise ValueError("Empty Container") 622 # Some advertising 623 self.meta.set_generator_default() 624 # Synchronize data with container 625 container = self.container 626 for path, part in self.__xmlparts.items(): 627 if part is not None: 628 container.set_part(path, part.serialize(pretty)) 629 # Save the container 630 container.save(target, packaging=packaging, backup=backup) 631 632 @property 633 def content(self) -> Content: 634 content: Content | None = self.get_part(ODF_CONTENT) # type:ignore 635 if content is None: 636 raise ValueError("Empty Content") 637 return content 638 639 @property 640 def styles(self) -> Styles: 641 styles: Styles | None = self.get_part(ODF_STYLES) # type:ignore 642 if styles is None: 643 raise ValueError("Empty Styles") 644 return styles 645 646 # Styles over several parts 647 648 def get_styles( 649 self, 650 family: str | bytes = "", 651 automatic: bool = False, 652 ) -> list[Style | Element]: 653 # compatibility with old versions: 654 655 if isinstance(family, bytes): 656 family = bytes_to_str(family) 657 return self.content.get_styles(family=family) + self.styles.get_styles( 658 family=family, automatic=automatic 659 ) 660 661 def get_style( 662 self, 663 family: str, 664 name_or_element: str | Style | None = None, 665 display_name: str | None = None, 666 ) -> Style | None: 667 """Return the style uniquely identified by the name/family pair. If 668 the argument is already a style object, it will return it. 669 670 If the name is None, the default style is fetched. 671 672 If the name is not the internal name but the name you gave in a 673 desktop application, use display_name instead. 674 675 Arguments: 676 677 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 678 'number', 'page-layout', 'master-page' 679 680 name -- str or Element or None 681 682 display_name -- str 683 684 Return: Style or None if not found. 685 """ 686 # 1. content.xml 687 element = self.content.get_style( 688 family, name_or_element=name_or_element, display_name=display_name 689 ) 690 if element is not None: 691 return element 692 # 2. styles.xml 693 return self.styles.get_style( 694 family, 695 name_or_element=name_or_element, 696 display_name=display_name, 697 ) 698 699 @staticmethod 700 def _pseudo_style_attribute(style_element: Style | Element, attribute: str) -> Any: 701 if hasattr(style_element, attribute): 702 return getattr(style_element, attribute) 703 return "" 704 705 def _set_automatic_name(self, style: Style, family: str) -> None: 706 """Generate a name for the new automatic style.""" 707 if not hasattr(style, "name"): 708 # do nothing 709 return 710 styles = self.get_styles(family=family, automatic=True) 711 max_index = 0 712 for existing_style in styles: 713 if not hasattr(existing_style, "name"): 714 continue 715 if not existing_style.name.startswith(AUTOMATIC_PREFIX): 716 continue 717 try: 718 index = int(existing_style.name[len(AUTOMATIC_PREFIX) :]) 719 except ValueError: 720 continue 721 max_index = max(max_index, index) 722 723 style.name = f"{AUTOMATIC_PREFIX}{max_index+1}" 724 725 def _insert_style_get_common_styles( 726 self, 727 family: str, 728 name: str, 729 ) -> tuple[Any, Any]: 730 style_container = self.styles.get_element("office:styles") 731 existing = self.styles.get_style(family, name) 732 return existing, style_container 733 734 def _insert_style_get_automatic_styles( 735 self, 736 style: Style, 737 family: str, 738 name: str, 739 ) -> tuple[Any, Any]: 740 style_container = self.content.get_element("office:automatic-styles") 741 # A name ? 742 if name: 743 if hasattr(style, "name"): 744 style.name = name 745 existing = self.content.get_style(family, name) 746 else: 747 self._set_automatic_name(style, family) 748 existing = None 749 return existing, style_container 750 751 def _insert_style_get_default_styles( 752 self, 753 style: Style, 754 family: str, 755 name: str, 756 ) -> tuple[Any, Any]: 757 style_container = self.styles.get_element("office:styles") 758 style.tag = "style:default-style" 759 if name: 760 style.del_attribute("style:name") 761 existing = self.styles.get_style(family) 762 return existing, style_container 763 764 def _insert_style_get_master_page( 765 self, 766 family: str, 767 name: str, 768 ) -> tuple[Any, Any]: 769 style_container = self.styles.get_element("office:master-styles") 770 existing = self.styles.get_style(family, name) 771 return existing, style_container 772 773 def _insert_style_get_font_face_default( 774 self, 775 family: str, 776 name: str, 777 ) -> tuple[Any, Any]: 778 style_container = self.styles.get_element("office:font-face-decls") 779 existing = self.styles.get_style(family, name) 780 return existing, style_container 781 782 def _insert_style_get_font_face( 783 self, 784 family: str, 785 name: str, 786 ) -> tuple[Any, Any]: 787 style_container = self.content.get_element("office:font-face-decls") 788 existing = self.content.get_style(family, name) 789 return existing, style_container 790 791 def _insert_style_get_page_layout( 792 self, 793 family: str, 794 name: str, 795 ) -> tuple[Any, Any]: 796 # force to automatic 797 style_container = self.styles.get_element("office:automatic-styles") 798 existing = self.styles.get_style(family, name) 799 return existing, style_container 800 801 def _insert_style_get_draw_fill_image( 802 self, 803 name: str, 804 ) -> tuple[Any, Any]: 805 # special case for 'draw:fill-image' pseudo style 806 # not family and style_element.__class__.__name__ == "DrawFillImage" 807 style_container = self.styles.get_element("office:styles") 808 existing = self.styles.get_style("", name) 809 return existing, style_container 810 811 def _insert_style_standard( 812 self, 813 style: Style, 814 name: str, 815 family: str, 816 automatic: bool, 817 default: bool, 818 ) -> tuple[Any, Any]: 819 # Common style 820 if name and automatic is False and default is False: 821 return self._insert_style_get_common_styles(family, name) 822 # Automatic style 823 elif automatic is True and default is False: 824 return self._insert_style_get_automatic_styles(style, family, name) 825 # Default style 826 elif automatic is False and default is True: 827 return self._insert_style_get_default_styles(style, family, name) 828 else: 829 raise AttributeError("Invalid combination of arguments") 830 831 def insert_style( # noqa: C901 832 self, 833 style: Style | str, 834 name: str = "", 835 automatic: bool = False, 836 default: bool = False, 837 ) -> Any: 838 """Insert the given style object in the document, as required by the 839 style family and type. 840 841 The style is expected to be a common style with a name. In case it 842 was created with no name, the given can be set on the fly. 843 844 If automatic is True, the style will be inserted as an automatic 845 style. 846 847 If default is True, the style will be inserted as a default style and 848 would replace any existing default style of the same family. Any name 849 or display name would be ignored. 850 851 Automatic and default arguments are mutually exclusive. 852 853 All styles can't be used as default styles. Default styles are 854 allowed for the following families: paragraph, text, section, table, 855 table-column, table-row, table-cell, table-page, chart, drawing-page, 856 graphic, presentation, control and ruby. 857 858 Arguments: 859 860 style -- Style or str 861 862 name -- str 863 864 automatic -- bool 865 866 default -- bool 867 868 Return : style name -- str 869 """ 870 871 # if style is a str, assume it is the Style definition 872 if isinstance(style, str): 873 style_element: Style = Element.from_tag(style) # type: ignore 874 else: 875 style_element = style 876 if not isinstance(style_element, Element): 877 raise TypeError(f"Unknown Style type: '{style!r}'") 878 879 # Get family and name 880 family = self._pseudo_style_attribute(style_element, "family") 881 if not name: 882 name = self._pseudo_style_attribute(style_element, "name") 883 884 # Master page style 885 if family == "master-page": 886 existing, style_container = self._insert_style_get_master_page(family, name) 887 # Font face declarations 888 elif family == "font-face": 889 if default: 890 existing, style_container = self._insert_style_get_font_face_default( 891 family, name 892 ) 893 else: 894 existing, style_container = self._insert_style_get_font_face( 895 family, name 896 ) 897 # page layout style 898 elif family == "page-layout": 899 existing, style_container = self._insert_style_get_page_layout(family, name) 900 # Common style 901 elif family in FAMILY_ODF_STD or family in {"number"}: 902 existing, style_container = self._insert_style_standard( 903 style_element, name, family, automatic, default 904 ) 905 elif not family and style_element.__class__.__name__ == "DrawFillImage": 906 # special case for 'draw:fill-image' pseudo style 907 existing, style_container = self._insert_style_get_draw_fill_image(name) 908 # Invalid style 909 else: 910 raise ValueError( 911 "Invalid style: " 912 f"{style_element}, tag:{style_element.tag}, family:{family}" 913 ) 914 915 # Insert it! 916 if existing is not None: 917 style_container.delete(existing) 918 style_container.append(style_element) 919 return self._pseudo_style_attribute(style_element, "name") 920 921 def get_styled_elements(self, name: str = "") -> list[Element]: 922 """Brute-force to find paragraphs, tables, etc. using the given style 923 name (or all by default). 924 925 Arguments: 926 927 name -- str 928 929 Return: list 930 """ 931 # Header, footer, etc. have styles too 932 return self.content.root.get_styled_elements( 933 name 934 ) + self.styles.root.get_styled_elements(name) 935 936 def show_styles( 937 self, 938 automatic: bool = True, 939 common: bool = True, 940 properties: bool = False, 941 ) -> str: 942 infos = [] 943 for style in self.get_styles(): 944 try: 945 name = style.name # type: ignore 946 except AttributeError: 947 print("--------------") 948 print(style.__class__) 949 print(style.serialize()) 950 raise 951 if style.__class__.__name__ == "DrawFillImage": 952 family = "" 953 else: 954 family = str(style.family) # type: ignore 955 parent = style.parent 956 is_auto = parent and parent.tag == "office:automatic-styles" 957 if is_auto and automatic is False or not is_auto and common is False: 958 continue 959 is_used = bool(self.get_styled_elements(name)) 960 infos.append( 961 { 962 "type": "auto " if is_auto else "common", 963 "used": "y" if is_used else "n", 964 "family": family, 965 "parent": self._pseudo_style_attribute(style, "parent_style") or "", 966 "name": name or "", 967 "display_name": self._pseudo_style_attribute(style, "display_name") 968 or "", 969 "properties": style.get_properties() if properties else None, # type: ignore 970 } 971 ) 972 if not infos: 973 return "" 974 # Sort by family and name 975 infos.sort(key=itemgetter("family", "name")) 976 # Show common and used first 977 infos.sort(key=itemgetter("type", "used"), reverse=True) 978 max_family = str(max([len(x["family"]) for x in infos])) # type: ignore 979 max_parent = str(max([len(x["parent"]) for x in infos])) # type: ignore 980 formater = ( 981 "%(type)s used:%(used)s family:%(family)-0" 982 + max_family 983 + "s parent:%(parent)-0" 984 + max_parent 985 + "s name:%(name)s" 986 ) 987 output = [] 988 for info in infos: 989 line = formater % info 990 if info["display_name"]: 991 line += " display_name:" + info["display_name"] # type: ignore 992 output.append(line) 993 if info["properties"]: 994 for name, value in info["properties"].items(): # type: ignore 995 output.append(f" - {name}: {value}") 996 output.append("") 997 return "\n".join(output) 998 999 def delete_styles(self) -> int: 1000 """Remove all style information from content and all styles. 1001 1002 Return: number of deleted styles 1003 """ 1004 # First remove references to styles 1005 for element in self.get_styled_elements(): 1006 for attribute in ( 1007 "text:style-name", 1008 "draw:style-name", 1009 "draw:text-style-name", 1010 "table:style-name", 1011 "style:page-layout-name", 1012 ): 1013 try: 1014 element.del_attribute(attribute) 1015 except KeyError: 1016 continue 1017 # Then remove supposedly orphaned styles 1018 deleted = 0 1019 for style in self.get_styles(): 1020 if style.name is None: # type: ignore 1021 # Don't delete default styles 1022 continue 1023 # elif type(style) is odf_master_page: 1024 # # Don't suppress header and footer, just styling was removed 1025 # continue 1026 style.delete() 1027 deleted += 1 1028 return deleted 1029 1030 def merge_styles_from(self, document: Document) -> None: 1031 """Copy all the styles of a document into ourself. 1032 1033 Styles with the same type and name will be replaced, so only unique 1034 styles will be preserved. 1035 """ 1036 manifest = self.manifest 1037 document_manifest = document.manifest 1038 for style in document.get_styles(): 1039 tagname = style.tag 1040 family = self._pseudo_style_attribute(style, "family") 1041 stylename = style.name # type: ignore 1042 container = style.parent 1043 container_name = container.tag # type: ignore 1044 partname = container.parent.tag # type: ignore 1045 # The destination part 1046 if partname == "office:document-styles": 1047 part: Content | Styles = self.styles 1048 elif partname == "office:document-content": 1049 part = self.content 1050 else: 1051 raise NotImplementedError(partname) 1052 # Implemented containers 1053 if container_name not in { 1054 "office:styles", 1055 "office:automatic-styles", 1056 "office:master-styles", 1057 "office:font-face-decls", 1058 }: 1059 raise NotImplementedError(container_name) 1060 dest = part.get_element(f"//{container_name}") 1061 # Implemented style types 1062 # if tagname not in registered_styles: 1063 # raise NotImplementedError(tagname) 1064 duplicate = part.get_style(family, stylename) 1065 if duplicate is not None: 1066 duplicate.delete() 1067 dest.append(style) 1068 # Copy images from the header/footer 1069 if tagname == "style:master-page": 1070 query = "descendant::draw:image" 1071 for image in style.get_elements(query): 1072 url = image.url # type: ignore 1073 part_url = document.get_part(url) 1074 # Manually add the part to keep the name 1075 self.set_part(url, part_url) # type: ignore 1076 media_type = document_manifest.get_media_type(url) 1077 manifest.add_full_path(url, media_type) # type: ignore 1078 # Copy images from the fill-image 1079 elif tagname == "draw:fill-image": 1080 url = style.url # type: ignore 1081 part_url = document.get_part(url) 1082 self.set_part(url, part_url) # type: ignore 1083 media_type = document_manifest.get_media_type(url) 1084 manifest.add_full_path(url, media_type) # type: ignore 1085 1086 def add_page_break_style(self) -> None: 1087 """Ensure that the document contains the style required for a manual page break. 1088 1089 Then a manual page break can be added to the document with: 1090 from paragraph import PageBreak 1091 ... 1092 document.body.append(PageBreak()) 1093 1094 Note: this style uses the property 'fo:break-after', another 1095 possibility could be the property 'fo:break-before' 1096 """ 1097 if existing := self.get_style( # noqa: SIM102 1098 family="paragraph", 1099 name_or_element="odfdopagebreak", 1100 ): 1101 if properties := existing.get_properties(): # noqa: SIM102 1102 if properties["fo:break-after"] == "page": 1103 return 1104 style = ( 1105 '<style:style style:family="paragraph" style:parent-style-name="Standard" ' 1106 'style:name="odfdopagebreak">' 1107 '<style:paragraph-properties fo:break-after="page"/></style:style>' 1108 ) 1109 self.insert_style(style, automatic=False)
Abstraction of the ODF document.
To create a new Document, several possibilities:
- Document() or Document("text") -> an "empty" document of type text
- Document("spreadsheet") -> an "empty" document of type spreadsheet
- Document("presentation") -> an "empty" document of type presentation
- Document("drawing") -> an "empty" document of type drawing
Meaning of “empty”: these documents are copies of the default
templates documents provided with this library, which, as templates,
are not really empty. It may be useful to clear the newly created
document: document.body.clear(), or adjust meta informations like
description or default language: document.meta.set_language('fr-FR')
If the argument is not a known template type, or is a Path, Document(file) will load the content of the ODF file.
To explicitly create a document from a custom template, use the Document.new(path) method whose argument is the path to the template file.
178 def __init__( 179 self, 180 target: str | bytes | Path | Container | io.BytesIO | None = "text", 181 ) -> None: 182 # Cache of XML parts 183 self.__xmlparts: dict[str, XmlPart] = {} 184 # Cache of the body 185 self.__body: Element | None = None 186 self.container: Container | None = None 187 if isinstance(target, bytes): 188 # eager conversion 189 target = bytes_to_str(target) 190 if target is None: 191 # empty document, you probably don't wnat this. 192 self.container = Container() 193 return 194 if isinstance(target, Path): 195 # let's assume we open a container on existing file 196 self.container = Container(target) 197 return 198 if isinstance(target, Container): 199 # special internal case, use an existing container 200 self.container = target 201 return 202 if isinstance(target, str): 203 if target in ODF_TEMPLATES: 204 # assuming a new document from templates 205 self.container = container_from_template(target) 206 return 207 # let's assume we open a container on existing file 208 self.container = Container(target) 209 return 210 if isinstance(target, io.BytesIO): 211 self.container = Container(target) 212 return 213 raise TypeError(f"Unknown Document source type: '{target!r}'")
224 @classmethod 225 def new(cls, template: str | Path | io.BytesIO = "text") -> Document: 226 """Create a Document from a template. 227 228 The template argument is expected to be the path to a ODF template. 229 230 Arguments: 231 232 template -- str or Path or file-like (io.BytesIO) 233 234 Return : ODF document -- Document 235 """ 236 container = container_from_template(template) 237 return cls(container)
Create a Document from a template.
The template argument is expected to be the path to a ODF template.
Arguments:
template -- str or Path or file-like (io.BytesIO)
Return : ODF document -- Document
241 @property 242 def path(self) -> Path | None: 243 """Shortcut to Document.Container.path.""" 244 if not self.container: 245 return None 246 return self.container.path
Shortcut to Document.Container.path.
257 def get_parts(self) -> list[str]: 258 """Return available part names with path inside the archive, e.g. 259 ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg'] 260 """ 261 if not self.container: 262 raise ValueError("Empty Container") 263 return self.container.get_parts()
Return available part names with path inside the archive, e.g. ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
265 def get_part(self, path: str) -> XmlPart | str | bytes | None: 266 """Return the bytes of the given part. The path is relative to the 267 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 268 269 'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts 270 to the real path, e.g. content.xml, and return a dedicated object with 271 its own API. 272 273 path formated as URI, so always use '/' separator 274 """ 275 if not self.container: 276 raise ValueError("Empty Container") 277 # "./ObjectReplacements/Object 1" 278 path = path.lstrip("./") 279 path = _get_part_path(path) 280 cls = _get_part_class(path) 281 # Raw bytes 282 if cls is None: 283 return self.container.get_part(path) 284 # XML part 285 part = self.__xmlparts.get(path) 286 if part is None: 287 self.__xmlparts[path] = part = cls(path, self.container) 288 return part
Return the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".
'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts to the real path, e.g. content.xml, and return a dedicated object with its own API.
path formated as URI, so always use '/' separator
290 def set_part(self, path: str, data: bytes) -> None: 291 """Set the bytes of the given part. The path is relative to the 292 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 293 294 path formated as URI, so always use '/' separator 295 """ 296 if not self.container: 297 raise ValueError("Empty Container") 298 # "./ObjectReplacements/Object 1" 299 path = path.lstrip("./") 300 path = _get_part_path(path) 301 cls = _get_part_class(path) 302 # XML part overwritten 303 if cls is not None: 304 with suppress(KeyError): 305 self.__xmlparts[path] 306 self.container.set_part(path, data)
Set the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".
path formated as URI, so always use '/' separator
308 def del_part(self, path: str) -> None: 309 """Mark a part for deletion. The path is relative to the archive, 310 e.g. "Pictures/1003200258912EB1C3.jpg" 311 """ 312 if not self.container: 313 raise ValueError("Empty Container") 314 path = _get_part_path(path) 315 cls = _get_part_class(path) 316 if path == ODF_MANIFEST or cls is not None: 317 raise ValueError(f"part '{path}' is mandatory") 318 self.container.del_part(path)
Mark a part for deletion. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg"
332 def get_type(self) -> str: 333 """Get the ODF type (also called class) of this document. 334 335 Return: 'chart', 'database', 'formula', 'graphics', 336 'graphics-template', 'image', 'presentation', 337 'presentation-template', 'spreadsheet', 'spreadsheet-template', 338 'text', 'text-master', 'text-template' or 'text-web' 339 """ 340 # The mimetype must be with the form: 341 # application/vnd.oasis.opendocument.text 342 343 # Isolate and return the last part 344 return self.mimetype.rsplit(".", 1)[-1]
Get the ODF type (also called class) of this document.
Return: 'chart', 'database', 'formula', 'graphics', 'graphics-template', 'image', 'presentation', 'presentation-template', 'spreadsheet', 'spreadsheet-template', 'text', 'text-master', 'text-template' or 'text-web'
346 @property 347 def body(self) -> Element: 348 """Return the body element of the content part, where actual content 349 is stored. 350 """ 351 if self.__body is None: 352 self.__body = self.content.body 353 return self.__body
Return the body element of the content part, where actual content is stored.
355 @property 356 def meta(self) -> Meta: 357 """Return the meta part (meta.xml) of the document, where meta data 358 are stored.""" 359 metadata = self.get_part(ODF_META) 360 if metadata is None or not isinstance(metadata, Meta): 361 raise ValueError("Empty Meta") 362 return metadata
Return the meta part (meta.xml) of the document, where meta data are stored.
364 @property 365 def manifest(self) -> Manifest: 366 """Return the manifest part (manifest.xml) of the document.""" 367 manifest = self.get_part(ODF_MANIFEST) 368 if manifest is None or not isinstance(manifest, Manifest): 369 raise ValueError("Empty Manifest") 370 return manifest
Return the manifest part (manifest.xml) of the document.
445 def get_formatted_text(self, rst_mode: bool = False) -> str: 446 """Return content as text, with some formatting.""" 447 # For the moment, only "type='text'" 448 doc_type = self.get_type() 449 if doc_type == "spreadsheet": 450 return self._tables_csv() 451 if doc_type in { 452 "text", 453 "text-template", 454 "presentation", 455 "presentation-template", 456 }: 457 return self._formatted_text(rst_mode) 458 raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")
Return content as text, with some formatting.
495 def get_formated_meta(self) -> str: 496 """Return meta informations as text, with some formatting.""" 497 result: list[str] = [] 498 499 # Simple values 500 def print_info(name: str, value: Any) -> None: 501 if value: 502 result.append(f"{name}: {value}") 503 504 meta = self.meta 505 print_info("Title", meta.get_title()) 506 print_info("Subject", meta.get_subject()) 507 print_info("Language", meta.get_language()) 508 print_info("Modification date", meta.get_modification_date()) 509 print_info("Creation date", meta.get_creation_date()) 510 print_info("Initial creator", meta.get_initial_creator()) 511 print_info("Keyword", meta.get_keywords()) 512 print_info("Editing duration", meta.get_editing_duration()) 513 print_info("Editing cycles", meta.get_editing_cycles()) 514 print_info("Generator", meta.get_generator()) 515 516 # Statistic 517 result.append("Statistic:") 518 statistic = meta.get_statistic() 519 if statistic: 520 for name, data in statistic.items(): 521 result.append(f" - {name[5:].replace('-', ' ').capitalize()}: {data}") 522 523 # User defined metadata 524 result.append("User defined metadata:") 525 user_metadata = meta.get_user_defined_metadata() 526 for name, data2 in user_metadata.items(): 527 result.append(f" - {name}: {data2}") 528 529 # And the description 530 print_info("Description", meta.get_description()) 531 532 return "\n".join(result) + "\n"
Return meta informations as text, with some formatting.
534 def add_file(self, path_or_file: str | Path) -> str: 535 """Insert a file from a path or a file-like object in the container. 536 537 Return the full path to reference in the content. 538 539 Arguments: 540 541 path_or_file -- str or Path or file-like 542 543 Return: str (URI) 544 """ 545 if not self.container: 546 raise ValueError("Empty Container") 547 name = "" 548 # Folder for added files (FIXME hard-coded and copied) 549 manifest = self.manifest 550 medias = manifest.get_paths() 551 # uuid = str(uuid4()) 552 553 if isinstance(path_or_file, (str, Path)): 554 path = Path(path_or_file) 555 extension = path.suffix.lower() 556 name = f"{path.stem}{extension}" 557 if posixpath.join("Pictures", name) in medias: 558 name = f"{path.stem}_{uuid4()}{extension}" 559 else: 560 path = None 561 name = getattr(path_or_file, "name", None) 562 if not name: 563 name = str(uuid4()) 564 media_type, _encoding = guess_type(name) 565 if not media_type: 566 media_type = "application/octet-stream" 567 if manifest.get_media_type("Pictures/") is None: 568 manifest.add_full_path("Pictures/") 569 full_path = posixpath.join("Pictures", name) 570 if path is None: 571 self.container.set_part(full_path, path_or_file.read()) 572 else: 573 self.container.set_part(full_path, path.read_bytes()) 574 manifest.add_full_path(full_path, media_type) 575 return full_path
Insert a file from a path or a file-like object in the container.
Return the full path to reference in the content.
Arguments:
path_or_file -- str or Path or file-like
Return: str (URI)
577 @property 578 def clone(self) -> Document: 579 """Return an exact copy of the document. 580 581 Return: Document 582 """ 583 clone = object.__new__(self.__class__) 584 for name in self.__dict__: 585 if name == "_Document__body": 586 setattr(clone, name, None) 587 elif name == "_Document__xmlparts": 588 setattr(clone, name, {}) 589 elif name == "container": 590 if not self.container: 591 raise ValueError("Empty Container") 592 setattr(clone, name, self.container.clone) 593 else: 594 value = deepcopy(getattr(self, name)) 595 setattr(clone, name, value) 596 return clone
Return an exact copy of the document.
Return: Document
598 def save( 599 self, 600 target: str | Path | io.BytesIO | None = None, 601 packaging: str = "zip", 602 pretty: bool = False, 603 backup: bool = False, 604 ) -> None: 605 """Save the document, at the same place it was opened or at the given 606 target path. Target can also be a file-like object. It can be saved 607 as a Zip file (default) or as files in a folder (for debugging 608 purpose). XML parts can be pretty printed. 609 610 Arguments: 611 612 target -- str or file-like object 613 614 packaging -- 'zip' or 'folder' 615 616 pretty -- bool 617 618 backup -- bool 619 """ 620 if not self.container: 621 raise ValueError("Empty Container") 622 # Some advertising 623 self.meta.set_generator_default() 624 # Synchronize data with container 625 container = self.container 626 for path, part in self.__xmlparts.items(): 627 if part is not None: 628 container.set_part(path, part.serialize(pretty)) 629 # Save the container 630 container.save(target, packaging=packaging, backup=backup)
Save the document, at the same place it was opened or at the given target path. Target can also be a file-like object. It can be saved as a Zip file (default) or as files in a folder (for debugging purpose). XML parts can be pretty printed.
Arguments:
target -- str or file-like object
packaging -- 'zip' or 'folder'
pretty -- bool
backup -- bool
648 def get_styles( 649 self, 650 family: str | bytes = "", 651 automatic: bool = False, 652 ) -> list[Style | Element]: 653 # compatibility with old versions: 654 655 if isinstance(family, bytes): 656 family = bytes_to_str(family) 657 return self.content.get_styles(family=family) + self.styles.get_styles( 658 family=family, automatic=automatic 659 )
661 def get_style( 662 self, 663 family: str, 664 name_or_element: str | Style | None = None, 665 display_name: str | None = None, 666 ) -> Style | None: 667 """Return the style uniquely identified by the name/family pair. If 668 the argument is already a style object, it will return it. 669 670 If the name is None, the default style is fetched. 671 672 If the name is not the internal name but the name you gave in a 673 desktop application, use display_name instead. 674 675 Arguments: 676 677 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 678 'number', 'page-layout', 'master-page' 679 680 name -- str or Element or None 681 682 display_name -- str 683 684 Return: Style or None if not found. 685 """ 686 # 1. content.xml 687 element = self.content.get_style( 688 family, name_or_element=name_or_element, display_name=display_name 689 ) 690 if element is not None: 691 return element 692 # 2. styles.xml 693 return self.styles.get_style( 694 family, 695 name_or_element=name_or_element, 696 display_name=display_name, 697 )
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in a desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number', 'page-layout', 'master-page'
name -- str or Element or None
display_name -- str
Return: Style or None if not found.
831 def insert_style( # noqa: C901 832 self, 833 style: Style | str, 834 name: str = "", 835 automatic: bool = False, 836 default: bool = False, 837 ) -> Any: 838 """Insert the given style object in the document, as required by the 839 style family and type. 840 841 The style is expected to be a common style with a name. In case it 842 was created with no name, the given can be set on the fly. 843 844 If automatic is True, the style will be inserted as an automatic 845 style. 846 847 If default is True, the style will be inserted as a default style and 848 would replace any existing default style of the same family. Any name 849 or display name would be ignored. 850 851 Automatic and default arguments are mutually exclusive. 852 853 All styles can't be used as default styles. Default styles are 854 allowed for the following families: paragraph, text, section, table, 855 table-column, table-row, table-cell, table-page, chart, drawing-page, 856 graphic, presentation, control and ruby. 857 858 Arguments: 859 860 style -- Style or str 861 862 name -- str 863 864 automatic -- bool 865 866 default -- bool 867 868 Return : style name -- str 869 """ 870 871 # if style is a str, assume it is the Style definition 872 if isinstance(style, str): 873 style_element: Style = Element.from_tag(style) # type: ignore 874 else: 875 style_element = style 876 if not isinstance(style_element, Element): 877 raise TypeError(f"Unknown Style type: '{style!r}'") 878 879 # Get family and name 880 family = self._pseudo_style_attribute(style_element, "family") 881 if not name: 882 name = self._pseudo_style_attribute(style_element, "name") 883 884 # Master page style 885 if family == "master-page": 886 existing, style_container = self._insert_style_get_master_page(family, name) 887 # Font face declarations 888 elif family == "font-face": 889 if default: 890 existing, style_container = self._insert_style_get_font_face_default( 891 family, name 892 ) 893 else: 894 existing, style_container = self._insert_style_get_font_face( 895 family, name 896 ) 897 # page layout style 898 elif family == "page-layout": 899 existing, style_container = self._insert_style_get_page_layout(family, name) 900 # Common style 901 elif family in FAMILY_ODF_STD or family in {"number"}: 902 existing, style_container = self._insert_style_standard( 903 style_element, name, family, automatic, default 904 ) 905 elif not family and style_element.__class__.__name__ == "DrawFillImage": 906 # special case for 'draw:fill-image' pseudo style 907 existing, style_container = self._insert_style_get_draw_fill_image(name) 908 # Invalid style 909 else: 910 raise ValueError( 911 "Invalid style: " 912 f"{style_element}, tag:{style_element.tag}, family:{family}" 913 ) 914 915 # Insert it! 916 if existing is not None: 917 style_container.delete(existing) 918 style_container.append(style_element) 919 return self._pseudo_style_attribute(style_element, "name")
Insert the given style object in the document, as required by the style family and type.
The style is expected to be a common style with a name. In case it was created with no name, the given can be set on the fly.
If automatic is True, the style will be inserted as an automatic style.
If default is True, the style will be inserted as a default style and would replace any existing default style of the same family. Any name or display name would be ignored.
Automatic and default arguments are mutually exclusive.
All styles can't be used as default styles. Default styles are allowed for the following families: paragraph, text, section, table, table-column, table-row, table-cell, table-page, chart, drawing-page, graphic, presentation, control and ruby.
Arguments:
style -- Style or str
name -- str
automatic -- bool
default -- bool
Return : style name -- str
921 def get_styled_elements(self, name: str = "") -> list[Element]: 922 """Brute-force to find paragraphs, tables, etc. using the given style 923 name (or all by default). 924 925 Arguments: 926 927 name -- str 928 929 Return: list 930 """ 931 # Header, footer, etc. have styles too 932 return self.content.root.get_styled_elements( 933 name 934 ) + self.styles.root.get_styled_elements(name)
Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).
Arguments:
name -- str
Return: list
936 def show_styles( 937 self, 938 automatic: bool = True, 939 common: bool = True, 940 properties: bool = False, 941 ) -> str: 942 infos = [] 943 for style in self.get_styles(): 944 try: 945 name = style.name # type: ignore 946 except AttributeError: 947 print("--------------") 948 print(style.__class__) 949 print(style.serialize()) 950 raise 951 if style.__class__.__name__ == "DrawFillImage": 952 family = "" 953 else: 954 family = str(style.family) # type: ignore 955 parent = style.parent 956 is_auto = parent and parent.tag == "office:automatic-styles" 957 if is_auto and automatic is False or not is_auto and common is False: 958 continue 959 is_used = bool(self.get_styled_elements(name)) 960 infos.append( 961 { 962 "type": "auto " if is_auto else "common", 963 "used": "y" if is_used else "n", 964 "family": family, 965 "parent": self._pseudo_style_attribute(style, "parent_style") or "", 966 "name": name or "", 967 "display_name": self._pseudo_style_attribute(style, "display_name") 968 or "", 969 "properties": style.get_properties() if properties else None, # type: ignore 970 } 971 ) 972 if not infos: 973 return "" 974 # Sort by family and name 975 infos.sort(key=itemgetter("family", "name")) 976 # Show common and used first 977 infos.sort(key=itemgetter("type", "used"), reverse=True) 978 max_family = str(max([len(x["family"]) for x in infos])) # type: ignore 979 max_parent = str(max([len(x["parent"]) for x in infos])) # type: ignore 980 formater = ( 981 "%(type)s used:%(used)s family:%(family)-0" 982 + max_family 983 + "s parent:%(parent)-0" 984 + max_parent 985 + "s name:%(name)s" 986 ) 987 output = [] 988 for info in infos: 989 line = formater % info 990 if info["display_name"]: 991 line += " display_name:" + info["display_name"] # type: ignore 992 output.append(line) 993 if info["properties"]: 994 for name, value in info["properties"].items(): # type: ignore 995 output.append(f" - {name}: {value}") 996 output.append("") 997 return "\n".join(output)
999 def delete_styles(self) -> int: 1000 """Remove all style information from content and all styles. 1001 1002 Return: number of deleted styles 1003 """ 1004 # First remove references to styles 1005 for element in self.get_styled_elements(): 1006 for attribute in ( 1007 "text:style-name", 1008 "draw:style-name", 1009 "draw:text-style-name", 1010 "table:style-name", 1011 "style:page-layout-name", 1012 ): 1013 try: 1014 element.del_attribute(attribute) 1015 except KeyError: 1016 continue 1017 # Then remove supposedly orphaned styles 1018 deleted = 0 1019 for style in self.get_styles(): 1020 if style.name is None: # type: ignore 1021 # Don't delete default styles 1022 continue 1023 # elif type(style) is odf_master_page: 1024 # # Don't suppress header and footer, just styling was removed 1025 # continue 1026 style.delete() 1027 deleted += 1 1028 return deleted
Remove all style information from content and all styles.
Return: number of deleted styles
1030 def merge_styles_from(self, document: Document) -> None: 1031 """Copy all the styles of a document into ourself. 1032 1033 Styles with the same type and name will be replaced, so only unique 1034 styles will be preserved. 1035 """ 1036 manifest = self.manifest 1037 document_manifest = document.manifest 1038 for style in document.get_styles(): 1039 tagname = style.tag 1040 family = self._pseudo_style_attribute(style, "family") 1041 stylename = style.name # type: ignore 1042 container = style.parent 1043 container_name = container.tag # type: ignore 1044 partname = container.parent.tag # type: ignore 1045 # The destination part 1046 if partname == "office:document-styles": 1047 part: Content | Styles = self.styles 1048 elif partname == "office:document-content": 1049 part = self.content 1050 else: 1051 raise NotImplementedError(partname) 1052 # Implemented containers 1053 if container_name not in { 1054 "office:styles", 1055 "office:automatic-styles", 1056 "office:master-styles", 1057 "office:font-face-decls", 1058 }: 1059 raise NotImplementedError(container_name) 1060 dest = part.get_element(f"//{container_name}") 1061 # Implemented style types 1062 # if tagname not in registered_styles: 1063 # raise NotImplementedError(tagname) 1064 duplicate = part.get_style(family, stylename) 1065 if duplicate is not None: 1066 duplicate.delete() 1067 dest.append(style) 1068 # Copy images from the header/footer 1069 if tagname == "style:master-page": 1070 query = "descendant::draw:image" 1071 for image in style.get_elements(query): 1072 url = image.url # type: ignore 1073 part_url = document.get_part(url) 1074 # Manually add the part to keep the name 1075 self.set_part(url, part_url) # type: ignore 1076 media_type = document_manifest.get_media_type(url) 1077 manifest.add_full_path(url, media_type) # type: ignore 1078 # Copy images from the fill-image 1079 elif tagname == "draw:fill-image": 1080 url = style.url # type: ignore 1081 part_url = document.get_part(url) 1082 self.set_part(url, part_url) # type: ignore 1083 media_type = document_manifest.get_media_type(url) 1084 manifest.add_full_path(url, media_type) # type: ignore
Copy all the styles of a document into ourself.
Styles with the same type and name will be replaced, so only unique styles will be preserved.
1086 def add_page_break_style(self) -> None: 1087 """Ensure that the document contains the style required for a manual page break. 1088 1089 Then a manual page break can be added to the document with: 1090 from paragraph import PageBreak 1091 ... 1092 document.body.append(PageBreak()) 1093 1094 Note: this style uses the property 'fo:break-after', another 1095 possibility could be the property 'fo:break-before' 1096 """ 1097 if existing := self.get_style( # noqa: SIM102 1098 family="paragraph", 1099 name_or_element="odfdopagebreak", 1100 ): 1101 if properties := existing.get_properties(): # noqa: SIM102 1102 if properties["fo:break-after"] == "page": 1103 return 1104 style = ( 1105 '<style:style style:family="paragraph" style:parent-style-name="Standard" ' 1106 'style:name="odfdopagebreak">' 1107 '<style:paragraph-properties fo:break-after="page"/></style:style>' 1108 ) 1109 self.insert_style(style, automatic=False)
Ensure that the document contains the style required for a manual page break.
Then a manual page break can be added to the document with: from paragraph import PageBreak ... document.body.append(PageBreak())
Note: this style uses the property 'fo:break-after', another possibility could be the property 'fo:break-before'
85class DrawFillImage(DrawImage): 86 _tag = "draw:fill-image" 87 _properties: tuple[PropDef, ...] = ( 88 PropDef("display_name", "draw:display-name"), 89 PropDef("name", "draw:name"), 90 PropDef("height", "svg:height"), 91 PropDef("width", "svg:width"), 92 ) 93 94 def __init__( 95 self, 96 name: str | None = None, 97 display_name: str | None = None, 98 height: str | None = None, 99 width: str | None = None, 100 **kwargs: Any, 101 ) -> None: 102 """The "draw:fill-image" element specifies a link to a bitmap 103 resource. Fill image are not available as automatic styles. 104 The "draw:fill-image" element is usable within the following element: 105 "office:styles" 106 107 Arguments: 108 109 name -- str 110 111 display_name -- str 112 113 height -- str 114 115 width -- str 116 """ 117 super().__init__(**kwargs) 118 if self._do_init: 119 self.name = name 120 self.display_name = display_name 121 self.height = height 122 self.width = width
The "draw:image" element represents an image. An image can be either:
- A link to an external resource or
- Embedded in the document (Not implemented in this version)
Warning: image elements must be stored in a frame "draw:frame", see Frame().
94 def __init__( 95 self, 96 name: str | None = None, 97 display_name: str | None = None, 98 height: str | None = None, 99 width: str | None = None, 100 **kwargs: Any, 101 ) -> None: 102 """The "draw:fill-image" element specifies a link to a bitmap 103 resource. Fill image are not available as automatic styles. 104 The "draw:fill-image" element is usable within the following element: 105 "office:styles" 106 107 Arguments: 108 109 name -- str 110 111 display_name -- str 112 113 height -- str 114 115 width -- str 116 """ 117 super().__init__(**kwargs) 118 if self._do_init: 119 self.name = name 120 self.display_name = display_name 121 self.height = height 122 self.width = width
The "draw:fill-image" element specifies a link to a bitmap resource. Fill image are not available as automatic styles. The "draw:fill-image" element is usable within the following element: "office:styles"
Arguments:
name -- str
display_name -- str
height -- str
width -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
315class DrawGroup(Element, AnchorMix, ZMix, PosMix): 316 """The DrawGroup "draw:g" element represents a group of drawing shapes. 317 318 Warning: implementation is currently minimal. 319 320 Drawing shapes contained by a "draw:g" element that is itself 321 contained by a "draw:a" element, act as hyperlinks using the 322 xlink:href attribute of the containing "draw:a" element. If the 323 included drawing shapes are themselves contained within "draw:a" 324 elements, then the xlink:href attributes of those "draw:a" elements 325 act as the hyperlink information for the shapes they contain. 326 327 The "draw:g" element has the following attributes: draw:caption-id, 328 draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, 329 presentation:class-names, presentation:style-name, svg:y, 330 table:end-cell-address, table:end-x, table:end-y, 331 table:table-background, text:anchor-page-number, text:anchor-type, 332 and xml:id. 333 334 The "draw:g" element has the following child elements: "dr3d:scene", 335 "draw:a", "draw:caption", "draw:circle", "draw:connector", 336 "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", 337 "draw:g", "draw:glue-point", "draw:line", "draw:measure", 338 "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", 339 "draw:rect", "draw:regular-polygon", "office:event-listeners", 340 "svg:desc" and "svg:title". 341 """ 342 343 _tag = "draw:g" 344 _properties: tuple[PropDef, ...] = ( 345 PropDef("draw_id", "draw:id"), 346 PropDef("caption_id", "draw:caption-id"), 347 PropDef("draw_class_names", "draw:class-names"), 348 PropDef("name", "draw:name"), 349 PropDef("style", "draw:style-name"), 350 # ('z_index', 'draw:z-index'), 351 PropDef("presentation_class_names", "presentation:class-names"), 352 PropDef("presentation_style", "presentation:style-name"), 353 PropDef("table_end_cell", "table:end-cell-address"), 354 PropDef("table_end_x", "table:end-x"), 355 PropDef("table_end_y", "table:end-y"), 356 PropDef("table_background", "table:table-background"), 357 # ('anchor_page', 'text:anchor-page-number'), 358 # ('anchor_type', 'text:anchor-type'), 359 PropDef("xml_id", "xml:id"), 360 PropDef("pos_x", "svg:x"), 361 PropDef("pos_y", "svg:y"), 362 ) 363 364 def __init__( 365 self, 366 name: str | None = None, 367 draw_id: str | None = None, 368 style: str | None = None, 369 position: tuple | None = None, 370 z_index: int = 0, 371 anchor_type: str | None = None, 372 anchor_page: int | None = None, 373 presentation_style: str | None = None, 374 **kwargs: Any, 375 ) -> None: 376 super().__init__(**kwargs) 377 if self._do_init: 378 if z_index is not None: 379 self.z_index = z_index 380 if name: 381 self.name = name 382 if draw_id is not None: 383 self.draw_id = draw_id 384 if style is not None: 385 self.style = style 386 if position is not None: 387 self.position = position 388 if anchor_type: 389 self.anchor_type = anchor_type 390 if anchor_page is not None: 391 self.anchor_page = anchor_page 392 if presentation_style is not None: 393 self.presentation_style = presentation_style
The DrawGroup "draw:g" element represents a group of drawing shapes.
Warning: implementation is currently minimal.
Drawing shapes contained by a "draw:g" element that is itself contained by a "draw:a" element, act as hyperlinks using the xlink:href attribute of the containing "draw:a" element. If the included drawing shapes are themselves contained within "draw:a" elements, then the xlink:href attributes of those "draw:a" elements act as the hyperlink information for the shapes they contain.
The "draw:g" element has the following attributes: draw:caption-id, draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, presentation:class-names, presentation:style-name, svg:y, table:end-cell-address, table:end-x, table:end-y, table:table-background, text:anchor-page-number, text:anchor-type, and xml:id.
The "draw:g" element has the following child elements: "dr3d:scene", "draw:a", "draw:caption", "draw:circle", "draw:connector", "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", "draw:g", "draw:glue-point", "draw:line", "draw:measure", "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", "draw:rect", "draw:regular-polygon", "office:event-listeners", "svg:desc" and "svg:title".
364 def __init__( 365 self, 366 name: str | None = None, 367 draw_id: str | None = None, 368 style: str | None = None, 369 position: tuple | None = None, 370 z_index: int = 0, 371 anchor_type: str | None = None, 372 anchor_page: int | None = None, 373 presentation_style: str | None = None, 374 **kwargs: Any, 375 ) -> None: 376 super().__init__(**kwargs) 377 if self._do_init: 378 if z_index is not None: 379 self.z_index = z_index 380 if name: 381 self.name = name 382 if draw_id is not None: 383 self.draw_id = draw_id 384 if style is not None: 385 self.style = style 386 if position is not None: 387 self.position = position 388 if anchor_type: 389 self.anchor_type = anchor_type 390 if anchor_page is not None: 391 self.anchor_page = anchor_page 392 if presentation_style is not None: 393 self.presentation_style = presentation_style
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.AnchorMix
- ANCHOR_VALUE_CHOICE
- anchor_type
- anchor_page
- odfdo.frame.ZMix
- z_index
- odfdo.frame.PosMix
- position
31class DrawImage(Element): 32 """The "draw:image" element represents an image. An image can be 33 either: 34 - A link to an external resource or 35 - Embedded in the document (Not implemented in this version) 36 37 Warning: image elements must be stored in a frame "draw:frame", 38 see Frame(). 39 """ 40 41 _tag = "draw:image" 42 _properties: tuple[PropDef, ...] = ( 43 PropDef("url", "xlink:href"), 44 PropDef("type", "xlink:type"), 45 PropDef("show", "xlink:show"), 46 PropDef("actuate", "xlink:actuate"), 47 PropDef("filter_name", "draw:filter-name"), 48 ) 49 50 def __init__( 51 self, 52 url: str = "", 53 xlink_type: str = "simple", 54 show: str = "embed", 55 actuate: str = "onLoad", 56 filter_name: str | None = None, 57 **kwargs: Any, 58 ) -> None: 59 """Initialisation of an DrawImage. 60 61 Arguments: 62 63 url -- str 64 65 type -- str 66 67 show -- str 68 69 actuate -- str 70 71 filter_name -- str 72 """ 73 super().__init__(**kwargs) 74 if self._do_init: 75 self.url = url 76 self.type = xlink_type 77 self.show = show 78 self.actuate = actuate 79 self.filter_name = filter_name
The "draw:image" element represents an image. An image can be either:
- A link to an external resource or
- Embedded in the document (Not implemented in this version)
Warning: image elements must be stored in a frame "draw:frame", see Frame().
50 def __init__( 51 self, 52 url: str = "", 53 xlink_type: str = "simple", 54 show: str = "embed", 55 actuate: str = "onLoad", 56 filter_name: str | None = None, 57 **kwargs: Any, 58 ) -> None: 59 """Initialisation of an DrawImage. 60 61 Arguments: 62 63 url -- str 64 65 type -- str 66 67 show -- str 68 69 actuate -- str 70 71 filter_name -- str 72 """ 73 super().__init__(**kwargs) 74 if self._do_init: 75 self.url = url 76 self.type = xlink_type 77 self.show = show 78 self.actuate = actuate 79 self.filter_name = filter_name
Initialisation of an DrawImage.
Arguments:
url -- str
type -- str
show -- str
actuate -- str
filter_name -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
33class DrawPage(Element): 34 """ODF draw page "draw:page", for pages of presentation and drawings.""" 35 36 _tag = "draw:page" 37 _properties = ( 38 PropDef("name", "draw:name"), 39 PropDef("draw_id", "draw:id"), 40 PropDef("master_page", "draw:master-page-name"), 41 PropDef( 42 "presentation_page_layout", "presentation:presentation-page-layout-name" 43 ), 44 PropDef("style", "draw:style-name"), 45 ) 46 47 def __init__( 48 self, 49 draw_id: str | None = None, 50 name: str | None = None, 51 master_page: str | None = None, 52 presentation_page_layout: str | None = None, 53 style: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 """ 57 Arguments: 58 59 draw_id -- str 60 61 name -- str 62 63 master_page -- str 64 65 presentation_page_layout -- str 66 67 style -- str 68 """ 69 super().__init__(**kwargs) 70 if self._do_init: 71 if draw_id: 72 self.draw_id = draw_id 73 if name: 74 self.name = name 75 if master_page: 76 self.master_page = master_page 77 if presentation_page_layout: 78 self.presentation_page_layout = presentation_page_layout 79 if style: 80 self.style = style 81 82 def set_transition( 83 self, 84 smil_type: str, 85 subtype: str | None = None, 86 dur: str = "2s", 87 ) -> None: 88 # Create the new animation 89 anim_page = AnimPar(presentation_node_type="timing-root") 90 anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin") 91 transition = AnimTransFilter( 92 smil_dur=dur, smil_type=smil_type, smil_subtype=subtype 93 ) 94 anim_page.append(anim_begin) 95 anim_begin.append(transition) 96 97 # Replace when already a transition: 98 # anim:seq => After the frame's transition 99 # cf page 349 of OpenDocument-v1.0-os.pdf 100 # Conclusion: We must delete the first child 'anim:par' 101 existing = self.get_element("anim:par") 102 if existing: 103 self.delete(existing) 104 self.append(anim_page) 105 106 def get_shapes(self) -> list[Element]: 107 query = "(descendant::" + "|descendant::".join(registered_shapes) + ")" 108 return self.get_elements(query) 109 110 def get_formatted_text(self, context: dict | None = None) -> str: 111 result: list[str] = [] 112 for child in self.children: 113 if child.tag == "presentation:notes": 114 # No need for an advanced odf_notes.get_formatted_text() 115 # because the text seems to be only contained in paragraphs 116 # and frames, that we already handle 117 for sub_child in child.children: 118 result.append(sub_child.get_formatted_text(context)) 119 result.append("\n") 120 result.append(child.get_formatted_text(context)) 121 result.append("\n") 122 return "".join(result)
ODF draw page "draw:page", for pages of presentation and drawings.
47 def __init__( 48 self, 49 draw_id: str | None = None, 50 name: str | None = None, 51 master_page: str | None = None, 52 presentation_page_layout: str | None = None, 53 style: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 """ 57 Arguments: 58 59 draw_id -- str 60 61 name -- str 62 63 master_page -- str 64 65 presentation_page_layout -- str 66 67 style -- str 68 """ 69 super().__init__(**kwargs) 70 if self._do_init: 71 if draw_id: 72 self.draw_id = draw_id 73 if name: 74 self.name = name 75 if master_page: 76 self.master_page = master_page 77 if presentation_page_layout: 78 self.presentation_page_layout = presentation_page_layout 79 if style: 80 self.style = style
Arguments:
draw_id -- str
name -- str
master_page -- str
presentation_page_layout -- str
style -- str
82 def set_transition( 83 self, 84 smil_type: str, 85 subtype: str | None = None, 86 dur: str = "2s", 87 ) -> None: 88 # Create the new animation 89 anim_page = AnimPar(presentation_node_type="timing-root") 90 anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin") 91 transition = AnimTransFilter( 92 smil_dur=dur, smil_type=smil_type, smil_subtype=subtype 93 ) 94 anim_page.append(anim_begin) 95 anim_begin.append(transition) 96 97 # Replace when already a transition: 98 # anim:seq => After the frame's transition 99 # cf page 349 of OpenDocument-v1.0-os.pdf 100 # Conclusion: We must delete the first child 'anim:par' 101 existing = self.get_element("anim:par") 102 if existing: 103 self.delete(existing) 104 self.append(anim_page)
110 def get_formatted_text(self, context: dict | None = None) -> str: 111 result: list[str] = [] 112 for child in self.children: 113 if child.tag == "presentation:notes": 114 # No need for an advanced odf_notes.get_formatted_text() 115 # because the text seems to be only contained in paragraphs 116 # and frames, that we already handle 117 for sub_child in child.children: 118 result.append(sub_child.get_formatted_text(context)) 119 result.append("\n") 120 result.append(child.get_formatted_text(context)) 121 result.append("\n") 122 return "".join(result)
This function should return a beautiful version of the text.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
304class Element(CachedElement): 305 """Super class of all ODF classes. 306 307 Representation of an XML element. Abstraction of the XML library behind. 308 """ 309 310 _tag: str = "" 311 _caching: bool = False 312 _properties: tuple[PropDef, ...] = () 313 314 def __init__(self, **kwargs: Any) -> None: 315 tag_or_elem = kwargs.pop("tag_or_elem", None) 316 if tag_or_elem is None: 317 # Instance for newly created object: create new lxml element and 318 # continue by subclass __init__ 319 # If the tag key word exists, make a custom element 320 self._do_init = True 321 tag = kwargs.pop("tag", self._tag) 322 self.__element = self.make_etree_element(tag) 323 else: 324 # called with an existing lxml element, sould be a result of 325 # from_tag() casting, do not execute the subclass __init__ 326 if not isinstance(tag_or_elem, _Element): 327 raise TypeError(f'"{type(tag_or_elem)}" is not an element node') 328 self._do_init = False 329 self.__element = tag_or_elem 330 331 def __repr__(self) -> str: 332 return f"<{self.__class__.__name__} tag={self.tag}>" 333 334 def __str__(self) -> str: 335 return self.text_recursive 336 337 @classmethod 338 def from_tag(cls, tag_or_elem: str | _Element) -> Element: 339 """Element class and subclass factory. 340 341 Turn an lxml Element or ODF string tag into an ODF XML Element 342 of the relevant class. 343 344 Arguments: 345 346 tag_or_elem -- ODF str tag or lxml.Element 347 348 Return: Element (or subclass) instance 349 """ 350 if isinstance(tag_or_elem, str): 351 # assume the argument is a prefix:name tag 352 elem = cls.make_etree_element(tag_or_elem) 353 else: 354 elem = tag_or_elem 355 klass = _class_registry.get(elem.tag, cls) 356 return klass(tag_or_elem=elem) 357 358 @classmethod 359 def from_tag_for_clone( 360 cls: type, 361 tree_element: _Element, 362 cache: tuple | None, 363 ) -> Element: 364 tag = to_str(tree_element.tag) 365 klass = _class_registry.get(tag, cls) 366 element = klass(tag_or_elem=tree_element) 367 if cache and element._caching: 368 element._tmap = cache[0] 369 element._cmap = cache[1] 370 if len(cache) == 3: 371 element._rmap = cache[2] 372 return element 373 374 @staticmethod 375 def make_etree_element(tag: str) -> _Element: 376 if not isinstance(tag, str): 377 raise TypeError(f"Tag is not str: {tag!r}") 378 tag = tag.strip() 379 if not tag: 380 raise ValueError("Tag is empty") 381 if "<" not in tag: 382 # Qualified name 383 # XXX don't build the element from scratch or lxml will pollute with 384 # repeated namespace declarations 385 tag = f"<{tag}/>" 386 # XML fragment 387 root = fromstring(NAMESPACES_XML % str_to_bytes(tag)) 388 return root[0] 389 390 @staticmethod 391 def _generic_attrib_getter(attr_name: str, family: str | None = None) -> Callable: 392 name = _get_lxml_tag(attr_name) 393 394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value) 406 407 return getter 408 409 @staticmethod 410 def _generic_attrib_setter(attr_name: str, family: str | None = None) -> Callable: 411 name = _get_lxml_tag(attr_name) 412 413 def setter(self: Element, value: Any) -> None: 414 try: 415 if family and self.family != family: # type: ignore 416 return None 417 except AttributeError: 418 return None 419 if value is None: 420 with contextlib.suppress(KeyError): 421 del self.__element.attrib[name] 422 return 423 if isinstance(value, bool): 424 value = Boolean.encode(value) 425 self.__element.set(name, str(value)) 426 427 return setter 428 429 @classmethod 430 def _define_attribut_property(cls: type[Element]) -> None: 431 for prop in cls._properties: 432 setattr( 433 cls, 434 prop.name, 435 property( 436 cls._generic_attrib_getter(prop.attr, prop.family or None), 437 cls._generic_attrib_setter(prop.attr, prop.family or None), 438 None, 439 f"Get/set the attribute {prop.attr}", 440 ), 441 ) 442 443 @staticmethod 444 def _make_before_regex( 445 before: str | None, 446 after: str | None, 447 ) -> re.Pattern: 448 # 1) before xor after is not None 449 if before is not None: 450 return re.compile(before) 451 else: 452 if after is None: 453 raise ValueError("Both 'before' and 'after' are None") 454 return re.compile(after) 455 456 @staticmethod 457 def _search_negative_position( 458 xpath_result: list, 459 regex: re.Pattern, 460 ) -> tuple[str, re.Match]: 461 # Found the last text that matches the regex 462 text = None 463 for a_text in xpath_result: 464 if regex.search(str(a_text)) is not None: 465 text = a_text 466 if text is None: 467 raise ValueError(f"Text not found: '{xpath_result}'") 468 if not isinstance(text, str): 469 raise TypeError(f"Text not found or text not of type str: '{text}'") 470 return text, list(regex.finditer(text))[-1] 471 472 @staticmethod 473 def _search_positive_position( 474 xpath_result: list, 475 regex: re.Pattern, 476 position: int, 477 ) -> tuple[str, re.Match]: 478 # Found the last text that matches the regex 479 count = 0 480 for text in xpath_result: 481 found_nb = len(regex.findall(str(text))) 482 if found_nb + count >= position + 1: 483 break 484 count += found_nb 485 else: 486 raise ValueError(f"Text not found: '{xpath_result}'") 487 if not isinstance(text, str): 488 raise TypeError(f"Text not found or text not of type str: '{text}'") 489 return text, list(regex.finditer(text))[position - count] 490 491 def _insert_before_after( 492 self, 493 current: _Element, 494 element: _Element, 495 before: str | None, 496 after: str | None, 497 position: int, 498 xpath_text: XPath, 499 ) -> tuple[int, str]: 500 regex = self._make_before_regex(before, after) 501 xpath_result = xpath_text(current) 502 if not isinstance(xpath_result, list): 503 raise TypeError("Bad XPath result") 504 # position = -1 505 if position < 0: 506 text, sre = self._search_negative_position(xpath_result, regex) 507 # position >= 0 508 else: 509 text, sre = self._search_positive_position(xpath_result, regex, position) 510 # Compute pos 511 if before is None: 512 pos = sre.end() 513 else: 514 pos = sre.start() 515 return pos, text 516 517 def _insert_find_text( 518 self, 519 current: _Element, 520 element: _Element, 521 before: str | None, 522 after: str | None, 523 position: int, 524 xpath_text: XPath, 525 ) -> tuple[int, str]: 526 # Find the text 527 xpath_result = xpath_text(current) 528 if not isinstance(xpath_result, list): 529 raise TypeError("Bad XPath result") 530 count = 0 531 for text in xpath_result: 532 if not isinstance(text, str): 533 continue 534 found_nb = len(text) 535 if found_nb + count >= position: 536 break 537 count += found_nb 538 else: 539 raise ValueError("Text not found") 540 # We insert before the character 541 pos = position - count 542 return pos, text 543 544 def _insert( 545 self, 546 element: Element, 547 before: str | None = None, 548 after: str | None = None, 549 position: int = 0, 550 main_text: bool = False, 551 ) -> None: 552 """Insert an element before or after the characters in the text which 553 match the regex before/after. 554 555 When the regex matches more of one part of the text, position can be 556 set to choice which part must be used. If before and after are None, 557 we use only position that is the number of characters. If position is 558 positive and before=after=None, we insert before the position 559 character. But if position=-1, we insert after the last character. 560 561 562 Arguments: 563 564 element -- Element 565 566 before -- str regex 567 568 after -- str regex 569 570 position -- int 571 """ 572 # not implemented: if main_text is True, filter out the annotations texts in computation. 573 current = self.__element 574 xelement = element.__element 575 576 if main_text: 577 xpath_text = _xpath_text_main_descendant 578 else: 579 xpath_text = _xpath_text_descendant 580 581 # 1) before xor after is not None 582 if (before is not None) ^ (after is not None): 583 pos, text = self._insert_before_after( 584 current, 585 xelement, 586 before, 587 after, 588 position, 589 xpath_text, 590 ) 591 # 2) before=after=None => only with position 592 elif before is None and after is None: 593 # Hack if position is negative => quickly 594 if position < 0: 595 current.append(xelement) 596 return 597 pos, text = self._insert_find_text( 598 current, 599 xelement, 600 before, 601 after, 602 position, 603 xpath_text, 604 ) 605 else: 606 raise ValueError("bad combination of arguments") 607 608 # Compute new texts 609 text_before = text[:pos] if text[:pos] else None 610 text_after = text[pos:] if text[pos:] else None 611 612 # Insert! 613 parent = text.getparent() # type: ignore 614 if text.is_text: # type: ignore 615 parent.text = text_before 616 element.tail = text_after 617 parent.insert(0, xelement) 618 else: 619 parent.addnext(xelement) 620 parent.tail = text_before 621 element.tail = text_after 622 623 def _insert_between( # noqa: C901 624 self, 625 element: Element, 626 from_: str, 627 to: str, 628 ) -> None: 629 """Insert the given empty element to wrap the text beginning with 630 "from_" and ending with "to". 631 632 Example 1: '<p>toto tata titi</p> 633 634 We want to insert a link around "tata". 635 636 Result 1: '<p>toto <a>tata</a> titi</p> 637 638 Example 2: '<p><span>toto</span> tata titi</p> 639 640 We want to insert a link around "tata". 641 642 Result 2: '<p><span>toto</span> <a>tata</a> titi</p> 643 644 Example 3: '<p>toto <span> tata </span> titi</p>' 645 646 We want to insert a link from "tata" to "titi" included. 647 648 Result 3: '<p>toto <span> </span>' 649 '<a><span>tata </span> titi</a></p>' 650 651 Example 4: '<p>toto <span>tata titi</span> tutu</p>' 652 653 We want to insert a link from "titi" to "tutu" 654 655 Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>' 656 '<a> tutu</a></p>' 657 658 Example 5: '<p>toto <span>tata titi</span> ' 659 '<span>tutu tyty</span></p>' 660 661 We want to insert a link from "titi" to "tutu" 662 663 Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> ' 664 '<a> <span>tutu</span></a><span> tyty</span></p>' 665 """ 666 current = self.__element 667 wrapper = element.__element 668 669 xpath_result = _xpath_text_descendant(current) 670 if not isinstance(xpath_result, list): 671 raise TypeError("Bad XPath result") 672 673 for text in xpath_result: 674 if not isinstance(text, str): 675 raise TypeError("Text not found or text not of type str") 676 if from_ not in text: 677 continue 678 from_index = text.index(from_) 679 text_before = text[:from_index] 680 text_after = text[from_index:] 681 from_container = text.getparent() # type: ignore 682 if not isinstance(from_container, _Element): 683 raise TypeError("Bad XPath result") 684 # Include from_index to match a single word 685 to_index = text.find(to, from_index) 686 if to_index >= 0: 687 # Simple case: "from" and "to" in the same element 688 to_end = to_index + len(to) 689 if text.is_text: # type: ignore 690 from_container.text = text_before 691 wrapper.text = text[to_index:to_end] 692 wrapper.tail = text[to_end:] 693 from_container.insert(0, wrapper) 694 else: 695 from_container.tail = text_before 696 wrapper.text = text[to_index:to_end] 697 wrapper.tail = text[to_end:] 698 parent = from_container.getparent() 699 index = parent.index(from_container) # type: ignore 700 parent.insert(index + 1, wrapper) # type: ignore 701 return 702 else: 703 # Exit to the second part where we search for the end text 704 break 705 else: 706 raise ValueError("Start text not found") 707 708 # The container is split in two 709 container2 = deepcopy(from_container) 710 if text.is_text: # type: ignore 711 from_container.text = text_before 712 from_container.tail = None 713 container2.text = text_after 714 from_container.tail = None 715 else: 716 from_container.tail = text_before 717 container2.tail = text_after 718 # Stack the copy into the surrounding element 719 wrapper.append(container2) 720 parent = from_container.getparent() 721 index = parent.index(from_container) # type: ignore 722 parent.insert(index + 1, wrapper) # type: ignore 723 724 xpath_result = _xpath_text_descendant(wrapper) 725 if not isinstance(xpath_result, list): 726 raise TypeError("Bad XPath result") 727 728 for text in xpath_result: 729 if not isinstance(text, str): 730 raise TypeError("Text not found or text not of type str") 731 if to not in text: 732 continue 733 to_end = text.index(to) + len(to) 734 text_before = text[:to_end] 735 text_after = text[to_end:] 736 container_to = text.getparent() # type: ignore 737 if not isinstance(container_to, _Element): 738 raise TypeError("Bad XPath result") 739 if text.is_text: # type: ignore 740 container_to.text = text_before 741 container_to.tail = text_after 742 else: 743 container_to.tail = text_before 744 next_one = container_to.getnext() 745 if next_one is None: 746 next_one = container_to.getparent() 747 next_one.tail = text_after # type: ignore 748 return 749 raise ValueError("End text not found") 750 751 @property 752 def tag(self) -> str: 753 """Get/set the underlying xml tag with the given qualified name. 754 755 Warning: direct change of tag does not change the element class. 756 757 Arguments: 758 759 qname -- str (e.g. "text:span") 760 """ 761 return _get_prefixed_name(self.__element.tag) 762 763 @tag.setter 764 def tag(self, qname: str) -> None: 765 self.__element.tag = _get_lxml_tag(qname) 766 767 def elements_repeated_sequence( 768 self, 769 xpath_instance: XPath, 770 name: str, 771 ) -> list[tuple[int, int]]: 772 """Utility method for table module.""" 773 lxml_tag = _get_lxml_tag_or_name(name) 774 element = self.__element 775 sub_elements = xpath_instance(element) 776 if not isinstance(sub_elements, list): 777 raise TypeError("Bad XPath result.") 778 result: list[tuple[int, int]] = [] 779 idx = -1 780 for sub_element in sub_elements: 781 if not isinstance(sub_element, _Element): 782 continue 783 idx += 1 784 value = sub_element.get(lxml_tag) 785 if value is None: 786 result.append((idx, 1)) 787 continue 788 try: 789 int_value = int(value) 790 except ValueError: 791 int_value = 1 792 result.append((idx, max(int_value, 1))) 793 return result 794 795 def get_elements(self, xpath_query: XPath | str) -> list[Element]: 796 cache: tuple | None = None 797 element = self.__element 798 if isinstance(xpath_query, str): 799 new_xpath_query = xpath_compile(xpath_query) 800 result = new_xpath_query(element) 801 else: 802 result = xpath_query(element) 803 if not isinstance(result, list): 804 raise TypeError("Bad XPath result") 805 806 if hasattr(self, "_tmap"): 807 if hasattr(self, "_rmap"): 808 cache = (self._tmap, self._cmap, self._rmap) 809 else: 810 cache = (self._tmap, self._cmap) 811 return [ 812 Element.from_tag_for_clone(e, cache) 813 for e in result 814 if isinstance(e, _Element) 815 ] 816 817 # fixme : need original get_element as wrapper of get_elements 818 819 def get_element(self, xpath_query: XPath | str) -> Element | None: 820 element = self.__element 821 result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES) 822 if result: 823 return Element.from_tag(result[0]) # type:ignore 824 return None 825 826 def _get_element_idx(self, xpath_query: XPath | str, idx: int) -> Element | None: 827 element = self.__element 828 result = element.xpath(f"({xpath_query})[{idx + 1}]", namespaces=ODF_NAMESPACES) 829 if result: 830 return Element.from_tag(result[0]) # type:ignore 831 return None 832 833 def _get_element_idx2(self, xpath_instance: XPath, idx: int) -> Element | None: 834 element = self.__element 835 result = xpath_instance(element, idx=idx + 1) 836 if result: 837 return Element.from_tag(result[0]) # type:ignore 838 return None 839 840 @property 841 def attributes(self) -> dict[str, str]: 842 return { 843 _get_prefixed_name(str(key)): str(value) 844 for key, value in self.__element.attrib.items() 845 } 846 847 def get_attribute(self, name: str) -> str | bool | None: 848 """Return the attribute value as type str | bool | None.""" 849 element = self.__element 850 lxml_tag = _get_lxml_tag_or_name(name) 851 value = element.get(lxml_tag) 852 if value is None: 853 return None 854 elif value in ("true", "false"): 855 return Boolean.decode(value) 856 return str(value) 857 858 def get_attribute_integer(self, name: str) -> int | None: 859 """Return either the attribute as type int, or None.""" 860 element = self.__element 861 lxml_tag = _get_lxml_tag_or_name(name) 862 value = element.get(lxml_tag) 863 if value is None: 864 return None 865 try: 866 return int(value) 867 except ValueError: 868 return None 869 870 def get_attribute_string(self, name: str) -> str | None: 871 """Return either the attribute as type str, or None.""" 872 element = self.__element 873 lxml_tag = _get_lxml_tag_or_name(name) 874 value = element.get(lxml_tag) 875 if value is None: 876 return None 877 return str(value) 878 879 def set_attribute(self, name: str, value: bool | str | None) -> None: 880 element = self.__element 881 lxml_tag = _get_lxml_tag_or_name(name) 882 if isinstance(value, bool): 883 value = Boolean.encode(value) 884 elif value is None: 885 with contextlib.suppress(KeyError): 886 del element.attrib[lxml_tag] 887 return 888 element.set(lxml_tag, str(value)) 889 890 def set_style_attribute(self, name: str, value: Element | str) -> None: 891 """Shortcut to accept a style object as a value.""" 892 if isinstance(value, Element): 893 value = str(value.name) # type:ignore 894 return self.set_attribute(name, value) 895 896 def del_attribute(self, name: str) -> None: 897 element = self.__element 898 lxml_tag = _get_lxml_tag_or_name(name) 899 del element.attrib[lxml_tag] 900 901 @property 902 def text(self) -> str: 903 """Get / set the text content of the element.""" 904 return self.__element.text or "" 905 906 @text.setter 907 def text(self, text: str | None) -> None: 908 if text is None: 909 text = "" 910 try: 911 self.__element.text = text 912 except TypeError as e: 913 raise TypeError(f'Str type expected: "{type(text)}"') from e 914 915 @property 916 def text_recursive(self) -> str: 917 return "".join(str(x) for x in self.__element.itertext()) 918 919 @property 920 def tail(self) -> str | None: 921 """Get / set the text immediately following the element.""" 922 return self.__element.tail 923 924 @tail.setter 925 def tail(self, text: str | None) -> None: 926 self.__element.tail = text or "" 927 928 def search(self, pattern: str) -> int | None: 929 """Return the first position of the pattern in the text content of 930 the element, or None if not found. 931 932 Python regular expression syntax applies. 933 934 Arguments: 935 936 pattern -- str 937 938 Return: int or None 939 """ 940 match = re.search(pattern, self.text_recursive) 941 if match is None: 942 return None 943 return match.start() 944 945 def match(self, pattern: str) -> bool: 946 """return True if the pattern is found one or more times anywhere in 947 the text content of the element. 948 949 Python regular expression syntax applies. 950 951 Arguments: 952 953 pattern -- str 954 955 Return: bool 956 """ 957 return self.search(pattern) is not None 958 959 def replace(self, pattern: str, new: str | None = None) -> int: 960 """Replace the pattern with the given text, or delete if text is an 961 empty string, and return the number of replacements. By default, only 962 return the number of occurences that would be replaced. 963 964 It cannot replace patterns found across several element, like a word 965 split into two consecutive spans. 966 967 Python regular expression syntax applies. 968 969 Arguments: 970 971 pattern -- str 972 973 new -- str 974 975 Return: int 976 """ 977 if not isinstance(pattern, str): 978 # Fail properly if the pattern is an non-ascii bytestring 979 pattern = str(pattern) 980 cpattern = re.compile(pattern) 981 count = 0 982 for text in self.xpath("descendant::text()"): 983 if new is None: 984 count += len(cpattern.findall(str(text))) 985 else: 986 new_text, number = cpattern.subn(new, str(text)) 987 container = text.parent 988 if text.is_text(): # type: ignore 989 container.text = new_text # type: ignore 990 else: 991 container.tail = new_text # type: ignore 992 count += number 993 return count 994 995 @property 996 def root(self) -> Element: 997 element = self.__element 998 tree = element.getroottree() 999 root = tree.getroot() 1000 return Element.from_tag(root) 1001 1002 @property 1003 def parent(self) -> Element | None: 1004 element = self.__element 1005 parent = element.getparent() 1006 if parent is None: 1007 # Already at root 1008 return None 1009 return Element.from_tag(parent) 1010 1011 @property 1012 def is_bound(self) -> bool: 1013 return self.parent is not None 1014 1015 # def get_next_sibling(self): 1016 # element = self.__element 1017 # next_one = element.getnext() 1018 # if next_one is None: 1019 # return None 1020 # return Element.from_tag(next_one) 1021 # 1022 # def get_prev_sibling(self): 1023 # element = self.__element 1024 # prev = element.getprevious() 1025 # if prev is None: 1026 # return None 1027 # return Element.from_tag(prev) 1028 1029 @property 1030 def children(self) -> list[Element]: 1031 element = self.__element 1032 return [ 1033 Element.from_tag(e) 1034 for e in element.iterchildren() 1035 if isinstance(e, _Element) 1036 ] 1037 1038 def index(self, child: Element) -> int: 1039 """Return the position of the child in this element. 1040 1041 Inspired by lxml 1042 """ 1043 return self.__element.index(child.__element) 1044 1045 @property 1046 def text_content(self) -> str: 1047 """Get / set the text of the embedded paragraph, including embeded 1048 annotations, cells... 1049 1050 Set create a paragraph if missing 1051 """ 1052 return "\n".join( 1053 child.text_recursive for child in self.get_elements("descendant::text:p") 1054 ) 1055 1056 @text_content.setter 1057 def text_content(self, text: str | None) -> None: 1058 paragraphs = self.get_elements("text:p") 1059 if not paragraphs: 1060 # E.g., text:p in draw:text-box in draw:frame 1061 paragraphs = self.get_elements("*/text:p") 1062 if paragraphs: 1063 paragraph = paragraphs.pop(0) 1064 for obsolete in paragraphs: 1065 obsolete.delete() 1066 else: 1067 paragraph = Element.from_tag("text:p") 1068 self.insert(paragraph, FIRST_CHILD) 1069 # As "text_content" returned all text nodes, "text_content" 1070 # will overwrite all text nodes and children that may contain them 1071 element = paragraph.__element 1072 # Clear but the attributes 1073 del element[:] 1074 element.text = text 1075 1076 def _erase_text_content(self) -> None: 1077 paragraphs = self.get_elements("text:p") 1078 if not paragraphs: 1079 # E.g., text:p in draw:text-box in draw:frame 1080 paragraphs = self.get_elements("*/text:p") 1081 if paragraphs: 1082 paragraphs.pop(0) 1083 for obsolete in paragraphs: 1084 obsolete.delete() 1085 1086 def is_empty(self) -> bool: 1087 """Check if the element is empty : no text, no children, no tail. 1088 1089 Return: Boolean 1090 """ 1091 element = self.__element 1092 if element.tail is not None: 1093 return False 1094 if element.text is not None: 1095 return False 1096 if list(element.iterchildren()): 1097 return False 1098 return True 1099 1100 def _get_successor(self, target: Element) -> tuple[Element | None, Element | None]: 1101 element = self.__element 1102 next_one = element.getnext() 1103 if next_one is not None: 1104 return Element.from_tag(next_one), target 1105 parent = self.parent 1106 if parent is None: 1107 return None, None 1108 return parent._get_successor(target.parent) # type:ignore 1109 1110 def _get_between_base( # noqa:C901 1111 self, 1112 tag1: Element, 1113 tag2: Element, 1114 ) -> list[Element]: 1115 def find_any_id(elem: Element) -> tuple[str, str, str]: 1116 elem_tag = elem.tag 1117 for attribute in ( 1118 "text:id", 1119 "text:change-id", 1120 "text:name", 1121 "office:name", 1122 "text:ref-name", 1123 "xml:id", 1124 ): 1125 idx = elem.get_attribute(attribute) 1126 if idx is not None: 1127 return elem_tag, attribute, str(idx) 1128 raise ValueError(f"No Id found in {elem.serialize()}") 1129 1130 def common_ancestor( 1131 tag1: str, 1132 attr1: str, 1133 val1: str, 1134 tag2: str, 1135 attr2: str, 1136 val2: str, 1137 ) -> Element | None: 1138 root = self.root 1139 request1 = f'descendant::{tag1}[@{attr1}="{val1}"]' 1140 request2 = f'descendant::{tag2}[@{attr2}="{val2}"]' 1141 ancestor = root.xpath(request1)[0] 1142 if ancestor is None: 1143 return None 1144 while True: 1145 # print "up", 1146 new_ancestor = ancestor.parent 1147 if new_ancestor is None: 1148 return None 1149 has_tag2 = new_ancestor.xpath(request2) 1150 ancestor = new_ancestor 1151 if not has_tag2: 1152 continue 1153 # print 'found' 1154 break 1155 # print up.serialize() 1156 return ancestor 1157 1158 elem1_tag, elem1_attr, elem1_val = find_any_id(tag1) 1159 elem2_tag, elem2_attr, elem2_val = find_any_id(tag2) 1160 ancestor_result = common_ancestor( 1161 elem1_tag, 1162 elem1_attr, 1163 elem1_val, 1164 elem2_tag, 1165 elem2_attr, 1166 elem2_val, 1167 ) 1168 if ancestor_result is None: 1169 raise RuntimeError(f"No common ancestor for {elem1_tag} {elem2_tag}") 1170 ancestor = ancestor_result.clone 1171 path1 = f'{elem1_tag}[@{elem1_attr}="{elem1_val}"]' 1172 path2 = f'{elem2_tag}[@{elem2_attr}="{elem2_val}"]' 1173 result = ancestor.clone 1174 for child in result.children: 1175 result.delete(child) 1176 result.text = "" 1177 result.tail = "" 1178 target = result 1179 current = ancestor.children[0] 1180 1181 state = 0 1182 while True: 1183 if current is None: 1184 raise RuntimeError(f"No current ancestor for {elem1_tag} {elem2_tag}") 1185 # print 'current', state, current.serialize() 1186 if state == 0: # before tag 1 1187 if current.xpath(f"descendant-or-self::{path1}"): 1188 if current.xpath(f"self::{path1}"): 1189 tail = current.tail 1190 if tail: 1191 # got a tail => the parent should be either t:p or t:h 1192 target.text = tail # type: ignore 1193 current, target = current._get_successor(target) # type: ignore 1194 state = 1 1195 continue 1196 # got T1 in chidren, need further analysis 1197 new_target = current.clone 1198 for child in new_target.children: 1199 new_target.delete(child) 1200 new_target.text = "" 1201 new_target.tail = "" 1202 target.append(new_target) # type: ignore 1203 target = new_target 1204 current = current.children[0] 1205 continue 1206 else: 1207 # before tag1 : forget element, go to next one 1208 current, target = current._get_successor(target) # type: ignore 1209 continue 1210 elif state == 1: # collect elements 1211 further = False 1212 if current.xpath(f"descendant-or-self::{path2}"): 1213 if current.xpath(f"self::{path2}"): 1214 # end of trip 1215 break 1216 # got T2 in chidren, need further analysis 1217 further = True 1218 # further analysis needed : 1219 if further: 1220 new_target = current.clone 1221 for child in new_target.children: 1222 new_target.delete(child) 1223 new_target.text = "" 1224 new_target.tail = "" 1225 target.append(new_target) # type: ignore 1226 target = new_target 1227 current = current.children[0] 1228 continue 1229 # collect 1230 target.append(current.clone) # type: ignore 1231 current, target = current._get_successor(target) # type: ignore 1232 continue 1233 # Now resu should be the "parent" of inserted parts 1234 # - a text:h or text:p sigle item (simple case) 1235 # - a upper element, with some text:p, text:h in it => need to be 1236 # stripped to have a list of text:p, text:h 1237 if result.tag in {"text:p", "text:h"}: 1238 inner = [result] 1239 else: 1240 inner = result.children 1241 return inner 1242 1243 def get_between( 1244 self, 1245 tag1: Element, 1246 tag2: Element, 1247 as_text: bool = False, 1248 clean: bool = True, 1249 no_header: bool = True, 1250 ) -> list | Element | str: 1251 """Returns elements between tag1 and tag2, tag1 and tag2 shall 1252 be unique and having an id attribute. 1253 (WARN: buggy if tag1/tag2 defines a malformed odf xml.) 1254 If as_text is True: returns the text content. 1255 If clean is True: suppress unwanted tags (deletions marks, ...) 1256 If no_header is True: existing text:h are changed in text:p 1257 By default: returns a list of Element, cleaned and without headers. 1258 1259 Implementation and standard retrictions: 1260 Only text:h and text:p sould be 'cut' by an insert tag, so inner parts 1261 of insert tags are: 1262 1263 - any text:h, text:p or sub tag of these 1264 1265 - some text, part of a parent text:h or text:p 1266 1267 Arguments: 1268 1269 tag1 -- Element 1270 1271 tag2 -- Element 1272 1273 as_text -- boolean 1274 1275 clean -- boolean 1276 1277 no_header -- boolean 1278 1279 Return: list of odf_paragraph or odf_header 1280 """ 1281 inner = self._get_between_base(tag1, tag2) 1282 1283 if clean: 1284 clean_tags = ( 1285 "text:change", 1286 "text:change-start", 1287 "text:change-end", 1288 "text:reference-mark", 1289 "text:reference-mark-start", 1290 "text:reference-mark-end", 1291 ) 1292 request_self = " | ".join(["self::%s" % c for c in clean_tags]) 1293 inner = [e for e in inner if not e.xpath(request_self)] 1294 request = " | ".join([f"descendant::{tag}" for tag in clean_tags]) 1295 for element in inner: 1296 to_del = element.xpath(request) 1297 for elem in to_del: 1298 if isinstance(elem, Element): 1299 element.delete(elem) 1300 if no_header: # crude replace t:h by t:p 1301 new_inner = [] 1302 for element in inner: 1303 if element.tag == "text:h": 1304 children = element.children 1305 text = element.__element.text 1306 para = Element.from_tag("text:p") 1307 para.text = text or "" 1308 for c in children: 1309 para.append(c) 1310 new_inner.append(para) 1311 else: 1312 new_inner.append(element) 1313 inner = new_inner 1314 if as_text: 1315 return "\n".join([e.get_formatted_text() for e in inner]) 1316 else: 1317 return inner 1318 1319 def insert( 1320 self, 1321 element: Element, 1322 xmlposition: int | None = None, 1323 position: int | None = None, 1324 start: bool = False, 1325 ) -> None: 1326 """Insert an element relatively to ourself. 1327 1328 Insert either using DOM vocabulary or by numeric position. 1329 If text start is True, insert the element before any existing text. 1330 1331 Position start at 0. 1332 1333 Arguments: 1334 1335 element -- Element 1336 1337 xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING 1338 or PREV_SIBLING 1339 1340 start -- Boolean 1341 1342 position -- int 1343 """ 1344 # child_tag = element.tag 1345 current = self.__element 1346 _element = element.__element 1347 if start: 1348 text = current.text 1349 if text is not None: 1350 current.text = None 1351 tail = _element.tail 1352 if tail is None: 1353 tail = text 1354 else: 1355 tail = tail + text 1356 _element.tail = tail 1357 position = 0 1358 if position is not None: 1359 current.insert(position, _element) 1360 elif xmlposition is FIRST_CHILD: 1361 current.insert(0, _element) 1362 elif xmlposition is LAST_CHILD: 1363 current.append(_element) 1364 elif xmlposition is NEXT_SIBLING: 1365 parent = current.getparent() 1366 index = parent.index(current) # type: ignore 1367 parent.insert(index + 1, _element) # type: ignore 1368 elif xmlposition is PREV_SIBLING: 1369 parent = current.getparent() 1370 index = parent.index(current) # type: ignore 1371 parent.insert(index, _element) # type: ignore 1372 else: 1373 raise ValueError("(xml)position must be defined") 1374 1375 def extend(self, odf_elements: Iterable[Element]) -> None: 1376 """Fast append elements at the end of ourself using extend.""" 1377 if odf_elements: 1378 current = self.__element 1379 elements = [element.__element for element in odf_elements] 1380 current.extend(elements) 1381 1382 def append(self, str_or_element: str | Element) -> None: 1383 """Insert element or text in the last position.""" 1384 current = self.__element 1385 if isinstance(str_or_element, str): 1386 # Has children ? 1387 children = list(current.iterchildren()) 1388 if children: 1389 # Append to tail of the last child 1390 last_child = children[-1] 1391 text = last_child.tail 1392 text = text if text is not None else "" 1393 text += str_or_element 1394 last_child.tail = text 1395 else: 1396 # Append to text of the element 1397 text = current.text 1398 text = text if text is not None else "" 1399 text += str_or_element 1400 current.text = text 1401 elif isinstance(str_or_element, Element): 1402 current.append(str_or_element.__element) 1403 else: 1404 raise TypeError(f'Element or string expected, not "{type(str_or_element)}"') 1405 1406 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 1407 """Delete the given element from the XML tree. If no element is given, 1408 "self" is deleted. The XML library may allow to continue to use an 1409 element now "orphan" as long as you have a reference to it. 1410 1411 if keep_tail is True (default), the tail text is not erased. 1412 1413 Arguments: 1414 1415 child -- Element 1416 1417 keep_tail -- boolean (default to True), True for most usages. 1418 """ 1419 if child is None: 1420 parent = self.parent 1421 if parent is None: 1422 raise ValueError(f"Can't delete the root element\n{self.serialize()}") 1423 child = self 1424 else: 1425 parent = self 1426 if keep_tail and child.__element.tail is not None: 1427 current = child.__element 1428 tail = str(current.tail) 1429 current.tail = None 1430 prev = current.getprevious() 1431 if prev is not None: 1432 if prev.tail is None: 1433 prev.tail = tail 1434 else: 1435 prev.tail += tail 1436 else: 1437 if parent.__element.text is None: 1438 parent.__element.text = tail 1439 else: 1440 parent.__element.text += tail 1441 parent.__element.remove(child.__element) 1442 1443 def replace_element(self, old_element: Element, new_element: Element) -> None: 1444 """Replaces in place a sub element with the element passed as second 1445 argument. 1446 1447 Warning : no clone for old element. 1448 """ 1449 current = self.__element 1450 current.replace(old_element.__element, new_element.__element) 1451 1452 def strip_elements( 1453 self, 1454 sub_elements: Element | Iterable[Element], 1455 ) -> Element | list: 1456 """Remove the tags of provided elements, keeping inner childs and text. 1457 1458 Return : the striped element. 1459 1460 Warning : no clone in sub_elements list. 1461 1462 Arguments: 1463 1464 sub_elements -- Element or list of Element 1465 """ 1466 if not sub_elements: 1467 return self 1468 if isinstance(sub_elements, Element): 1469 sub_elements = (sub_elements,) 1470 replacer = _get_lxml_tag("text:this-will-be-removed") 1471 for element in sub_elements: 1472 element.__element.tag = replacer 1473 strip = ("text:this-will-be-removed",) 1474 return self.strip_tags(strip=strip, default=None) 1475 1476 def strip_tags( 1477 self, 1478 strip: Iterable[str] | None = None, 1479 protect: Iterable[str] | None = None, 1480 default: str | None = "text:p", 1481 ) -> Element | list: 1482 """Remove the tags listed in strip, recursively, keeping inner childs 1483 and text. Tags listed in protect stop the removal one level depth. If 1484 the first level element is stripped, default is used to embed the 1485 content in the default element. If default is None and first level is 1486 striped, a list of text and children is returned. Return : the striped 1487 element. 1488 1489 strip_tags should be used by on purpose methods (strip_span ...) 1490 (Method name taken from lxml). 1491 1492 Arguments: 1493 1494 strip -- iterable list of str odf tags, or None 1495 1496 protect -- iterable list of str odf tags, or None 1497 1498 default -- str odf tag, or None 1499 1500 Return: 1501 1502 Element. 1503 """ 1504 if not strip: 1505 return self 1506 if not protect: 1507 protect = () 1508 protected = False 1509 element, modified = Element._strip_tags(self, strip, protect, protected) 1510 if modified and isinstance(element, list) and default: 1511 new = Element.from_tag(default) 1512 for content in element: 1513 if isinstance(content, Element): 1514 new.append(content) 1515 else: 1516 new.text = content 1517 element = new 1518 return element 1519 1520 @staticmethod 1521 def _strip_tags( # noqa:C901 1522 element: Element, 1523 strip: Iterable[str], 1524 protect: Iterable[str], 1525 protected: bool, 1526 ) -> tuple[Element | list, bool]: 1527 """Sub method for strip_tags().""" 1528 element_clone = element.clone 1529 modified = False 1530 children = [] 1531 if protect and element.tag in protect: 1532 protect_below = True 1533 else: 1534 protect_below = False 1535 for child in element_clone.children: 1536 striped_child, is_modified = Element._strip_tags( 1537 child, strip, protect, protect_below 1538 ) 1539 if is_modified: 1540 modified = True 1541 if isinstance(striped_child, list): 1542 children.extend(striped_child) 1543 else: 1544 children.append(striped_child) 1545 1546 text = element_clone.text 1547 tail = element_clone.tail 1548 if not protected and strip and element.tag in strip: 1549 element_result: list[Element | str] = [] 1550 if text is not None: 1551 element_result.append(text) 1552 for child in children: 1553 element_result.append(child) 1554 if tail is not None: 1555 element_result.append(tail) 1556 return (element_result, True) 1557 else: 1558 if not modified: 1559 return (element, False) 1560 element.clear() 1561 try: 1562 for key, value in element_clone.attributes.items(): 1563 element.set_attribute(key, value) 1564 except ValueError: 1565 sys.stderr.write(f"strip_tags(): bad attribute in {element_clone}\n") 1566 if text is not None: 1567 element.append(text) 1568 for child in children: 1569 element.append(child) 1570 if tail is not None: 1571 element.tail = tail 1572 return (element, True) 1573 1574 def xpath(self, xpath_query: str) -> list[Element | Text]: 1575 """Apply XPath query to the element and its subtree. Return list of 1576 Element or Text instances translated from the nodes found. 1577 """ 1578 element = self.__element 1579 xpath_instance = xpath_compile(xpath_query) 1580 elements = xpath_instance(element) 1581 result: list[Element | Text] = [] 1582 if hasattr(elements, "__iter__"): 1583 for obj in elements: # type: ignore 1584 if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)): 1585 result.append(Text(obj)) 1586 elif isinstance(obj, _Element): 1587 result.append(Element.from_tag(obj)) 1588 # else: 1589 # result.append(obj) 1590 return result 1591 1592 def clear(self) -> None: 1593 """Remove text, children and attributes from the element.""" 1594 self.__element.clear() 1595 if hasattr(self, "_tmap"): 1596 self._tmap: list[int] = [] 1597 if hasattr(self, "_cmap"): 1598 self._cmap: list[int] = [] 1599 if hasattr(self, "_rmap"): 1600 self._rmap: list[int] = [] 1601 if hasattr(self, "_indexes"): 1602 remember = False 1603 if "_rmap" in self._indexes: 1604 remember = True 1605 self._indexes: dict[str, dict] = {} 1606 self._indexes["_cmap"] = {} 1607 self._indexes["_tmap"] = {} 1608 if remember: 1609 self._indexes["_rmap"] = {} 1610 1611 @property 1612 def clone(self) -> Element: 1613 clone = deepcopy(self.__element) 1614 root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES) 1615 root.append(clone) 1616 return self.from_tag(clone) 1617 1618 # slow data = tostring(self.__element, encoding='unicode') 1619 # return self.from_tag(data) 1620 1621 @staticmethod 1622 def _strip_namespaces(data: str) -> str: 1623 """Remove xmlns:* fields from serialized XML.""" 1624 return re.sub(r' xmlns:\w*="[\w:\-\/\.#]*"', "", data) 1625 1626 def serialize(self, pretty: bool = False, with_ns: bool = False) -> str: 1627 """Return text serialization of XML element.""" 1628 # This copy bypasses serialization side-effects in lxml 1629 native = deepcopy(self.__element) 1630 data = tostring( 1631 native, with_tail=False, pretty_print=pretty, encoding="unicode" 1632 ) 1633 if with_ns: 1634 return data 1635 # Remove namespaces 1636 return self._strip_namespaces(data) 1637 1638 # Element helpers usable from any context 1639 1640 @property 1641 def document_body(self) -> Element | None: 1642 """Return the document body : 'office:body'""" 1643 return self.get_element("//office:body/*[1]") 1644 1645 @document_body.setter 1646 def document_body(self, new_body: Element) -> None: 1647 """Change in place the full document body content.""" 1648 body = self.document_body 1649 if body is None: 1650 raise ValueError("//office:body not found in document") 1651 tail = body.tail 1652 body.clear() 1653 for item in new_body.children: 1654 body.append(item) 1655 if tail: 1656 body.tail = tail 1657 1658 def get_formatted_text(self, context: dict | None = None) -> str: 1659 """This function should return a beautiful version of the text.""" 1660 return "" 1661 1662 def get_styled_elements(self, name: str = "") -> list[Element]: 1663 """Brute-force to find paragraphs, tables, etc. using the given style 1664 name (or all by default). 1665 1666 Arguments: 1667 1668 name -- str 1669 1670 Return: list 1671 """ 1672 # FIXME incomplete (and possibly inaccurate) 1673 return ( 1674 self._filtered_elements("descendant::*", text_style=name) 1675 + self._filtered_elements("descendant::*", draw_style=name) 1676 + self._filtered_elements("descendant::*", draw_text_style=name) 1677 + self._filtered_elements("descendant::*", table_style=name) 1678 + self._filtered_elements("descendant::*", page_layout=name) 1679 + self._filtered_elements("descendant::*", master_page=name) 1680 + self._filtered_elements("descendant::*", parent_style=name) 1681 ) 1682 1683 # Common attributes 1684 1685 def _get_inner_text(self, tag: str) -> str | None: 1686 element = self.get_element(tag) 1687 if element is None: 1688 return None 1689 return element.text 1690 1691 def _set_inner_text(self, tag: str, text: str) -> None: 1692 element = self.get_element(tag) 1693 if element is None: 1694 element = Element.from_tag(tag) 1695 self.append(element) 1696 element.text = text 1697 1698 # Dublin core 1699 1700 @property 1701 def dc_creator(self) -> str | None: 1702 """Get dc:creator value. 1703 1704 Return: str (or None if inexistant) 1705 """ 1706 return self._get_inner_text("dc:creator") 1707 1708 @dc_creator.setter 1709 def dc_creator(self, creator: str) -> None: 1710 """Set dc:creator value. 1711 1712 Arguments: 1713 1714 creator -- str 1715 """ 1716 self._set_inner_text("dc:creator", creator) 1717 1718 @property 1719 def dc_date(self) -> datetime | None: 1720 """Get the dc:date value. 1721 1722 Return: datetime (or None if inexistant) 1723 """ 1724 date = self._get_inner_text("dc:date") 1725 if date is None: 1726 return None 1727 return DateTime.decode(date) 1728 1729 @dc_date.setter 1730 def dc_date(self, date: datetime) -> None: 1731 """Set the dc:date value. 1732 1733 Arguments: 1734 1735 darz -- datetime 1736 """ 1737 self._set_inner_text("dc:date", DateTime.encode(date)) 1738 1739 # SVG 1740 1741 @property 1742 def svg_title(self) -> str | None: 1743 return self._get_inner_text("svg:title") 1744 1745 @svg_title.setter 1746 def svg_title(self, title: str) -> None: 1747 self._set_inner_text("svg:title", title) 1748 1749 @property 1750 def svg_description(self) -> str | None: 1751 return self._get_inner_text("svg:desc") 1752 1753 @svg_description.setter 1754 def svg_description(self, description: str) -> None: 1755 self._set_inner_text("svg:desc", description) 1756 1757 # Sections 1758 1759 def get_sections( 1760 self, 1761 style: str | None = None, 1762 content: str | None = None, 1763 ) -> list[Element]: 1764 """Return all the sections that match the criteria. 1765 1766 Arguments: 1767 1768 style -- str 1769 1770 content -- str regex 1771 1772 Return: list of Element 1773 """ 1774 return self._filtered_elements( 1775 "text:section", text_style=style, content=content 1776 ) 1777 1778 def get_section( 1779 self, 1780 position: int = 0, 1781 content: str | None = None, 1782 ) -> Element | None: 1783 """Return the section that matches the criteria. 1784 1785 Arguments: 1786 1787 position -- int 1788 1789 content -- str regex 1790 1791 Return: Element or None if not found 1792 """ 1793 return self._filtered_element( 1794 "descendant::text:section", position, content=content 1795 ) 1796 1797 # Paragraphs 1798 1799 def get_paragraphs( 1800 self, 1801 style: str | None = None, 1802 content: str | None = None, 1803 ) -> list[Element]: 1804 """Return all the paragraphs that match the criteria. 1805 1806 Arguments: 1807 1808 style -- str 1809 1810 content -- str regex 1811 1812 Return: list of Paragraph 1813 """ 1814 return self._filtered_elements( 1815 "descendant::text:p", text_style=style, content=content 1816 ) 1817 1818 def get_paragraph( 1819 self, 1820 position: int = 0, 1821 content: str | None = None, 1822 ) -> Element | None: 1823 """Return the paragraph that matches the criteria. 1824 1825 Arguments: 1826 1827 position -- int 1828 1829 content -- str regex 1830 1831 Return: Paragraph or None if not found 1832 """ 1833 return self._filtered_element("descendant::text:p", position, content=content) 1834 1835 # Span 1836 1837 def get_spans( 1838 self, 1839 style: str | None = None, 1840 content: str | None = None, 1841 ) -> list[Element]: 1842 """Return all the spans that match the criteria. 1843 1844 Arguments: 1845 1846 style -- str 1847 1848 content -- str regex 1849 1850 Return: list of Span 1851 """ 1852 return self._filtered_elements( 1853 "descendant::text:span", text_style=style, content=content 1854 ) 1855 1856 def get_span( 1857 self, 1858 position: int = 0, 1859 content: str | None = None, 1860 ) -> Element | None: 1861 """Return the span that matches the criteria. 1862 1863 Arguments: 1864 1865 position -- int 1866 1867 content -- str regex 1868 1869 Return: Span or None if not found 1870 """ 1871 return self._filtered_element( 1872 "descendant::text:span", position, content=content 1873 ) 1874 1875 # Headers 1876 1877 def get_headers( 1878 self, 1879 style: str | None = None, 1880 outline_level: str | None = None, 1881 content: str | None = None, 1882 ) -> list[Element]: 1883 """Return all the Headers that match the criteria. 1884 1885 Arguments: 1886 1887 style -- str 1888 1889 content -- str regex 1890 1891 Return: list of Header 1892 """ 1893 return self._filtered_elements( 1894 "descendant::text:h", 1895 text_style=style, 1896 outline_level=outline_level, 1897 content=content, 1898 ) 1899 1900 def get_header( 1901 self, 1902 position: int = 0, 1903 outline_level: str | None = None, 1904 content: str | None = None, 1905 ) -> Element | None: 1906 """Return the Header that matches the criteria. 1907 1908 Arguments: 1909 1910 position -- int 1911 1912 content -- str regex 1913 1914 Return: Header or None if not found 1915 """ 1916 return self._filtered_element( 1917 "descendant::text:h", 1918 position, 1919 outline_level=outline_level, 1920 content=content, 1921 ) 1922 1923 # Lists 1924 1925 def get_lists( 1926 self, 1927 style: str | None = None, 1928 content: str | None = None, 1929 ) -> list[Element]: 1930 """Return all the lists that match the criteria. 1931 1932 Arguments: 1933 1934 style -- str 1935 1936 content -- str regex 1937 1938 Return: list of List 1939 """ 1940 return self._filtered_elements( 1941 "descendant::text:list", text_style=style, content=content 1942 ) 1943 1944 def get_list( 1945 self, 1946 position: int = 0, 1947 content: str | None = None, 1948 ) -> Element | None: 1949 """Return the list that matches the criteria. 1950 1951 Arguments: 1952 1953 position -- int 1954 1955 content -- str regex 1956 1957 Return: List or None if not found 1958 """ 1959 return self._filtered_element( 1960 "descendant::text:list", position, content=content 1961 ) 1962 1963 # Frames 1964 1965 def get_frames( 1966 self, 1967 presentation_class: str | None = None, 1968 style: str | None = None, 1969 title: str | None = None, 1970 description: str | None = None, 1971 content: str | None = None, 1972 ) -> list[Element]: 1973 """Return all the frames that match the criteria. 1974 1975 Arguments: 1976 1977 presentation_class -- str 1978 1979 style -- str 1980 1981 title -- str regex 1982 1983 description -- str regex 1984 1985 content -- str regex 1986 1987 Return: list of Frame 1988 """ 1989 return self._filtered_elements( 1990 "descendant::draw:frame", 1991 presentation_class=presentation_class, 1992 draw_style=style, 1993 svg_title=title, 1994 svg_desc=description, 1995 content=content, 1996 ) 1997 1998 def get_frame( 1999 self, 2000 position: int = 0, 2001 name: str | None = None, 2002 presentation_class: str | None = None, 2003 title: str | None = None, 2004 description: str | None = None, 2005 content: str | None = None, 2006 ) -> Element | None: 2007 """Return the section that matches the criteria. 2008 2009 Arguments: 2010 2011 position -- int 2012 2013 name -- str 2014 2015 presentation_class -- str 2016 2017 title -- str regex 2018 2019 description -- str regex 2020 2021 content -- str regex 2022 2023 Return: Frame or None if not found 2024 """ 2025 return self._filtered_element( 2026 "descendant::draw:frame", 2027 position, 2028 draw_name=name, 2029 presentation_class=presentation_class, 2030 svg_title=title, 2031 svg_desc=description, 2032 content=content, 2033 ) 2034 2035 # Images 2036 2037 def get_images( 2038 self, 2039 style: str | None = None, 2040 url: str | None = None, 2041 content: str | None = None, 2042 ) -> list[Element]: 2043 """Return all the images matching the criteria. 2044 2045 Arguments: 2046 2047 style -- str 2048 2049 url -- str regex 2050 2051 content -- str regex 2052 2053 Return: list of Element 2054 """ 2055 return self._filtered_elements( 2056 "descendant::draw:image", text_style=style, url=url, content=content 2057 ) 2058 2059 def get_image( 2060 self, 2061 position: int = 0, 2062 name: str | None = None, 2063 url: str | None = None, 2064 content: str | None = None, 2065 ) -> Element | None: 2066 """Return the image matching the criteria. 2067 2068 Arguments: 2069 2070 position -- int 2071 2072 name -- str 2073 2074 url -- str regex 2075 2076 content -- str regex 2077 2078 Return: Element or None if not found 2079 """ 2080 # The frame is holding the name 2081 if name is not None: 2082 frame = self._filtered_element( 2083 "descendant::draw:frame", position, draw_name=name 2084 ) 2085 if frame is None: 2086 return None 2087 # The name is supposedly unique 2088 return frame.get_element("draw:image") 2089 return self._filtered_element( 2090 "descendant::draw:image", position, url=url, content=content 2091 ) 2092 2093 # Tables 2094 2095 def get_tables( 2096 self, 2097 style: str | None = None, 2098 content: str | None = None, 2099 ) -> list[Element]: 2100 """Return all the tables that match the criteria. 2101 2102 Arguments: 2103 2104 style -- str 2105 2106 content -- str regex 2107 2108 Return: list of Table 2109 """ 2110 return self._filtered_elements( 2111 "descendant::table:table", table_style=style, content=content 2112 ) 2113 2114 def get_table( 2115 self, 2116 position: int = 0, 2117 name: str | None = None, 2118 content: str | None = None, 2119 ) -> Element | None: 2120 """Return the table that matches the criteria. 2121 2122 Arguments: 2123 2124 position -- int 2125 2126 name -- str 2127 2128 content -- str regex 2129 2130 Return: Table or None if not found 2131 """ 2132 if name is None and content is None: 2133 result = self._filtered_element("descendant::table:table", position) 2134 else: 2135 result = self._filtered_element( 2136 "descendant::table:table", 2137 position, 2138 table_name=name, 2139 content=content, 2140 ) 2141 return result 2142 2143 # Named Range 2144 2145 def get_named_ranges(self) -> list[Element]: 2146 """Return all the tables named ranges. 2147 2148 Return: list of odf_named_range 2149 """ 2150 named_ranges = self.get_elements( 2151 "descendant::table:named-expressions/table:named-range" 2152 ) 2153 return named_ranges 2154 2155 def get_named_range(self, name: str) -> Element | None: 2156 """Return the named range of specified name, or None if not found. 2157 2158 Arguments: 2159 2160 name -- str 2161 2162 Return: NamedRange 2163 """ 2164 named_range = self.get_elements( 2165 f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]' 2166 ) 2167 if named_range: 2168 return named_range[0] 2169 else: 2170 return None 2171 2172 def append_named_range(self, named_range: Element) -> None: 2173 """Append the named range to the spreadsheet, replacing existing named 2174 range of same name if any. 2175 2176 Arguments: 2177 2178 named_range -- NamedRange 2179 """ 2180 if self.tag != "office:spreadsheet": 2181 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2182 named_expressions = self.get_element("table:named-expressions") 2183 if not named_expressions: 2184 named_expressions = Element.from_tag("table:named-expressions") 2185 self.append(named_expressions) 2186 # exists ? 2187 current = named_expressions.get_element( 2188 f'table:named-range[@table:name="{named_range.name}"][1]' # type:ignore 2189 ) 2190 if current: 2191 named_expressions.delete(current) 2192 named_expressions.append(named_range) 2193 2194 def delete_named_range(self, name: str) -> None: 2195 """Delete the Named Range of specified name from the spreadsheet. 2196 2197 Arguments: 2198 2199 name -- str 2200 """ 2201 if self.tag != "office:spreadsheet": 2202 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2203 named_range = self.get_named_range(name) 2204 if not named_range: 2205 return 2206 named_range.delete() 2207 named_expressions = self.get_element("table:named-expressions") 2208 if not named_expressions: 2209 return 2210 element = named_expressions.__element 2211 children = list(element.iterchildren()) 2212 if not children: 2213 self.delete(named_expressions) 2214 2215 # Notes 2216 2217 def get_notes( 2218 self, 2219 note_class: str | None = None, 2220 content: str | None = None, 2221 ) -> list[Element]: 2222 """Return all the notes that match the criteria. 2223 2224 Arguments: 2225 2226 note_class -- 'footnote' or 'endnote' 2227 2228 content -- str regex 2229 2230 Return: list of Note 2231 """ 2232 return self._filtered_elements( 2233 "descendant::text:note", note_class=note_class, content=content 2234 ) 2235 2236 def get_note( 2237 self, 2238 position: int = 0, 2239 note_id: str | None = None, 2240 note_class: str | None = None, 2241 content: str | None = None, 2242 ) -> Element | None: 2243 """Return the note that matches the criteria. 2244 2245 Arguments: 2246 2247 position -- int 2248 2249 note_id -- str 2250 2251 note_class -- 'footnote' or 'endnote' 2252 2253 content -- str regex 2254 2255 Return: Note or None if not found 2256 """ 2257 return self._filtered_element( 2258 "descendant::text:note", 2259 position, 2260 text_id=note_id, 2261 note_class=note_class, 2262 content=content, 2263 ) 2264 2265 # Annotations 2266 2267 def get_annotations( 2268 self, 2269 creator: str | None = None, 2270 start_date: datetime | None = None, 2271 end_date: datetime | None = None, 2272 content: str | None = None, 2273 ) -> list[Element]: 2274 """Return all the annotations that match the criteria. 2275 2276 Arguments: 2277 2278 creator -- str 2279 2280 start_date -- datetime instance 2281 2282 end_date -- datetime instance 2283 2284 content -- str regex 2285 2286 Return: list of Annotation 2287 """ 2288 annotations = [] 2289 for annotation in self._filtered_elements( 2290 "descendant::office:annotation", content=content 2291 ): 2292 if creator is not None and creator != annotation.dc_creator: 2293 continue 2294 date = annotation.dc_date 2295 if date is None: 2296 continue 2297 if start_date is not None and date < start_date: 2298 continue 2299 if end_date is not None and date >= end_date: 2300 continue 2301 annotations.append(annotation) 2302 return annotations 2303 2304 def get_annotation( 2305 self, 2306 position: int = 0, 2307 creator: str | None = None, 2308 start_date: datetime | None = None, 2309 end_date: datetime | None = None, 2310 content: str | None = None, 2311 name: str | None = None, 2312 ) -> Element | None: 2313 """Return the annotation that matches the criteria. 2314 2315 Arguments: 2316 2317 position -- int 2318 2319 creator -- str 2320 2321 start_date -- datetime instance 2322 2323 end_date -- datetime instance 2324 2325 content -- str regex 2326 2327 name -- str 2328 2329 Return: Annotation or None if not found 2330 """ 2331 if name is not None: 2332 return self._filtered_element( 2333 "descendant::office:annotation", 0, office_name=name 2334 ) 2335 annotations = self.get_annotations( 2336 creator=creator, start_date=start_date, end_date=end_date, content=content 2337 ) 2338 if not annotations: 2339 return None 2340 try: 2341 return annotations[position] 2342 except IndexError: 2343 return None 2344 2345 def get_annotation_ends(self) -> list[Element]: 2346 """Return all the annotation ends. 2347 2348 Return: list of Element 2349 """ 2350 return self._filtered_elements("descendant::office:annotation-end") 2351 2352 def get_annotation_end( 2353 self, 2354 position: int = 0, 2355 name: str | None = None, 2356 ) -> Element | None: 2357 """Return the annotation end that matches the criteria. 2358 2359 Arguments: 2360 2361 position -- int 2362 2363 name -- str 2364 2365 Return: Element or None if not found 2366 """ 2367 return self._filtered_element( 2368 "descendant::office:annotation-end", position, office_name=name 2369 ) 2370 2371 # office:names 2372 2373 def get_office_names(self) -> list[str]: 2374 """Return all the used office:name tags values of the element. 2375 2376 Return: list of unique str 2377 """ 2378 name_xpath_query = xpath_compile("//@office:name") 2379 response = name_xpath_query(self.__element) 2380 if not isinstance(response, list): 2381 return [] 2382 return list({str(name) for name in response if name}) 2383 2384 # Variables 2385 2386 def get_variable_decls(self) -> Element: 2387 """Return the container for variable declarations. Created if not 2388 found. 2389 2390 Return: Element 2391 """ 2392 variable_decls = self.get_element("//text:variable-decls") 2393 if variable_decls is None: 2394 body = self.document_body 2395 if not body: 2396 raise ValueError("Empty document.body") 2397 body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD) 2398 variable_decls = body.get_element("//text:variable-decls") 2399 2400 return variable_decls # type:ignore 2401 2402 def get_variable_decl_list(self) -> list[Element]: 2403 """Return all the variable declarations. 2404 2405 Return: list of Element 2406 """ 2407 return self._filtered_elements("descendant::text:variable-decl") 2408 2409 def get_variable_decl(self, name: str, position: int = 0) -> Element | None: 2410 """return the variable declaration for the given name. 2411 2412 Arguments: 2413 2414 name -- str 2415 2416 position -- int 2417 2418 return: Element or none if not found 2419 """ 2420 return self._filtered_element( 2421 "descendant::text:variable-decl", position, text_name=name 2422 ) 2423 2424 def get_variable_sets(self, name: str | None = None) -> list[Element]: 2425 """Return all the variable sets that match the criteria. 2426 2427 Arguments: 2428 2429 name -- str 2430 2431 Return: list of Element 2432 """ 2433 return self._filtered_elements("descendant::text:variable-set", text_name=name) 2434 2435 def get_variable_set(self, name: str, position: int = -1) -> Element | None: 2436 """Return the variable set for the given name (last one by default). 2437 2438 Arguments: 2439 2440 name -- str 2441 2442 position -- int 2443 2444 Return: Element or None if not found 2445 """ 2446 return self._filtered_element( 2447 "descendant::text:variable-set", position, text_name=name 2448 ) 2449 2450 def get_variable_set_value( 2451 self, 2452 name: str, 2453 value_type: str | None = None, 2454 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2455 """Return the last value of the given variable name. 2456 2457 Arguments: 2458 2459 name -- str 2460 2461 value_type -- 'boolean', 'currency', 'date', 'float', 2462 'percentage', 'string', 'time' or automatic 2463 2464 Return: most appropriate Python type 2465 """ 2466 variable_set = self.get_variable_set(name) 2467 if not variable_set: 2468 return None 2469 return variable_set.get_value(value_type) # type: ignore 2470 2471 # User fields 2472 2473 def get_user_field_decls(self) -> Element | None: 2474 """Return the container for user field declarations. Created if not 2475 found. 2476 2477 Return: Element 2478 """ 2479 user_field_decls = self.get_element("//text:user-field-decls") 2480 if user_field_decls is None: 2481 body = self.document_body 2482 if not body: 2483 raise ValueError("Empty document.body") 2484 body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD) 2485 user_field_decls = body.get_element("//text:user-field-decls") 2486 2487 return user_field_decls 2488 2489 def get_user_field_decl_list(self) -> list[Element]: 2490 """Return all the user field declarations. 2491 2492 Return: list of Element 2493 """ 2494 return self._filtered_elements("descendant::text:user-field-decl") 2495 2496 def get_user_field_decl(self, name: str, position: int = 0) -> Element | None: 2497 """return the user field declaration for the given name. 2498 2499 return: Element or none if not found 2500 """ 2501 return self._filtered_element( 2502 "descendant::text:user-field-decl", position, text_name=name 2503 ) 2504 2505 def get_user_field_value( 2506 self, name: str, value_type: str | None = None 2507 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2508 """Return the value of the given user field name. 2509 2510 Arguments: 2511 2512 name -- str 2513 2514 value_type -- 'boolean', 'currency', 'date', 'float', 2515 'percentage', 'string', 'time' or automatic 2516 2517 Return: most appropriate Python type 2518 """ 2519 user_field_decl = self.get_user_field_decl(name) 2520 if user_field_decl is None: 2521 return None 2522 return user_field_decl.get_value(value_type) # type: ignore 2523 2524 # User defined fields 2525 # They are fields who should contain a copy of a user defined medtadata 2526 2527 def get_user_defined_list(self) -> list[Element]: 2528 """Return all the user defined field declarations. 2529 2530 Return: list of Element 2531 """ 2532 return self._filtered_elements("descendant::text:user-defined") 2533 2534 def get_user_defined(self, name: str, position: int = 0) -> Element | None: 2535 """return the user defined declaration for the given name. 2536 2537 return: Element or none if not found 2538 """ 2539 return self._filtered_element( 2540 "descendant::text:user-defined", position, text_name=name 2541 ) 2542 2543 def get_user_defined_value( 2544 self, name: str, value_type: str | None = None 2545 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2546 """Return the value of the given user defined field name. 2547 2548 Arguments: 2549 2550 name -- str 2551 2552 value_type -- 'boolean', 'date', 'float', 2553 'string', 'time' or automatic 2554 2555 Return: most appropriate Python type 2556 """ 2557 user_defined = self.get_user_defined(name) 2558 if user_defined is None: 2559 return None 2560 return user_defined.get_value(value_type) # type: ignore 2561 2562 # Draw Pages 2563 2564 def get_draw_pages( 2565 self, 2566 style: str | None = None, 2567 content: str | None = None, 2568 ) -> list[Element]: 2569 """Return all the draw pages that match the criteria. 2570 2571 Arguments: 2572 2573 style -- str 2574 2575 content -- str regex 2576 2577 Return: list of DrawPage 2578 """ 2579 return self._filtered_elements( 2580 "descendant::draw:page", draw_style=style, content=content 2581 ) 2582 2583 def get_draw_page( 2584 self, 2585 position: int = 0, 2586 name: str | None = None, 2587 content: str | None = None, 2588 ) -> Element | None: 2589 """Return the draw page that matches the criteria. 2590 2591 Arguments: 2592 2593 position -- int 2594 2595 name -- str 2596 2597 content -- str regex 2598 2599 Return: DrawPage or None if not found 2600 """ 2601 return self._filtered_element( 2602 "descendant::draw:page", position, draw_name=name, content=content 2603 ) 2604 2605 # Links 2606 2607 def get_links( 2608 self, 2609 name: str | None = None, 2610 title: str | None = None, 2611 url: str | None = None, 2612 content: str | None = None, 2613 ) -> list[Element]: 2614 """Return all the links that match the criteria. 2615 2616 Arguments: 2617 2618 name -- str 2619 2620 title -- str 2621 2622 url -- str regex 2623 2624 content -- str regex 2625 2626 Return: list of Element 2627 """ 2628 return self._filtered_elements( 2629 "descendant::text:a", 2630 office_name=name, 2631 office_title=title, 2632 url=url, 2633 content=content, 2634 ) 2635 2636 def get_link( 2637 self, 2638 position: int = 0, 2639 name: str | None = None, 2640 title: str | None = None, 2641 url: str | None = None, 2642 content: str | None = None, 2643 ) -> Element | None: 2644 """Return the link that matches the criteria. 2645 2646 Arguments: 2647 2648 position -- int 2649 2650 name -- str 2651 2652 title -- str 2653 2654 url -- str regex 2655 2656 content -- str regex 2657 2658 Return: Element or None if not found 2659 """ 2660 return self._filtered_element( 2661 "descendant::text:a", 2662 position, 2663 office_name=name, 2664 office_title=title, 2665 url=url, 2666 content=content, 2667 ) 2668 2669 # Bookmarks 2670 2671 def get_bookmarks(self) -> list[Element]: 2672 """Return all the bookmarks. 2673 2674 Return: list of Element 2675 """ 2676 return self._filtered_elements("descendant::text:bookmark") 2677 2678 def get_bookmark( 2679 self, 2680 position: int = 0, 2681 name: str | None = None, 2682 ) -> Element | None: 2683 """Return the bookmark that matches the criteria. 2684 2685 Arguments: 2686 2687 position -- int 2688 2689 name -- str 2690 2691 Return: Bookmark or None if not found 2692 """ 2693 return self._filtered_element( 2694 "descendant::text:bookmark", position, text_name=name 2695 ) 2696 2697 def get_bookmark_starts(self) -> list[Element]: 2698 """Return all the bookmark starts. 2699 2700 Return: list of Element 2701 """ 2702 return self._filtered_elements("descendant::text:bookmark-start") 2703 2704 def get_bookmark_start( 2705 self, 2706 position: int = 0, 2707 name: str | None = None, 2708 ) -> Element | None: 2709 """Return the bookmark start that matches the criteria. 2710 2711 Arguments: 2712 2713 position -- int 2714 2715 name -- str 2716 2717 Return: Element or None if not found 2718 """ 2719 return self._filtered_element( 2720 "descendant::text:bookmark-start", position, text_name=name 2721 ) 2722 2723 def get_bookmark_ends(self) -> list[Element]: 2724 """Return all the bookmark ends. 2725 2726 Return: list of Element 2727 """ 2728 return self._filtered_elements("descendant::text:bookmark-end") 2729 2730 def get_bookmark_end( 2731 self, 2732 position: int = 0, 2733 name: str | None = None, 2734 ) -> Element | None: 2735 """Return the bookmark end that matches the criteria. 2736 2737 Arguments: 2738 2739 position -- int 2740 2741 name -- str 2742 2743 Return: Element or None if not found 2744 """ 2745 return self._filtered_element( 2746 "descendant::text:bookmark-end", position, text_name=name 2747 ) 2748 2749 # Reference marks 2750 2751 def get_reference_marks_single(self) -> list[Element]: 2752 """Return all the reference marks. Search only the tags 2753 text:reference-mark. 2754 Consider using : get_reference_marks() 2755 2756 Return: list of Element 2757 """ 2758 return self._filtered_elements("descendant::text:reference-mark") 2759 2760 def get_reference_mark_single( 2761 self, 2762 position: int = 0, 2763 name: str | None = None, 2764 ) -> Element | None: 2765 """Return the reference mark that matches the criteria. Search only the 2766 tags text:reference-mark. 2767 Consider using : get_reference_mark() 2768 2769 Arguments: 2770 2771 position -- int 2772 2773 name -- str 2774 2775 Return: Element or None if not found 2776 """ 2777 return self._filtered_element( 2778 "descendant::text:reference-mark", position, text_name=name 2779 ) 2780 2781 def get_reference_mark_starts(self) -> list[Element]: 2782 """Return all the reference mark starts. Search only the tags 2783 text:reference-mark-start. 2784 Consider using : get_reference_marks() 2785 2786 Return: list of Element 2787 """ 2788 return self._filtered_elements("descendant::text:reference-mark-start") 2789 2790 def get_reference_mark_start( 2791 self, 2792 position: int = 0, 2793 name: str | None = None, 2794 ) -> Element | None: 2795 """Return the reference mark start that matches the criteria. Search 2796 only the tags text:reference-mark-start. 2797 Consider using : get_reference_mark() 2798 2799 Arguments: 2800 2801 position -- int 2802 2803 name -- str 2804 2805 Return: Element or None if not found 2806 """ 2807 return self._filtered_element( 2808 "descendant::text:reference-mark-start", position, text_name=name 2809 ) 2810 2811 def get_reference_mark_ends(self) -> list[Element]: 2812 """Return all the reference mark ends. Search only the tags 2813 text:reference-mark-end. 2814 Consider using : get_reference_marks() 2815 2816 Return: list of Element 2817 """ 2818 return self._filtered_elements("descendant::text:reference-mark-end") 2819 2820 def get_reference_mark_end( 2821 self, 2822 position: int = 0, 2823 name: str | None = None, 2824 ) -> Element | None: 2825 """Return the reference mark end that matches the criteria. Search only 2826 the tags text:reference-mark-end. 2827 Consider using : get_reference_marks() 2828 2829 Arguments: 2830 2831 position -- int 2832 2833 name -- str 2834 2835 Return: Element or None if not found 2836 """ 2837 return self._filtered_element( 2838 "descendant::text:reference-mark-end", position, text_name=name 2839 ) 2840 2841 def get_reference_marks(self) -> list[Element]: 2842 """Return all the reference marks, either single position reference 2843 (text:reference-mark) or start of range reference 2844 (text:reference-mark-start). 2845 2846 Return: list of Element 2847 """ 2848 return self._filtered_elements( 2849 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2850 ) 2851 2852 def get_reference_mark( 2853 self, 2854 position: int = 0, 2855 name: str | None = None, 2856 ) -> Element | None: 2857 """Return the reference mark that match the criteria. Either single 2858 position reference mark (text:reference-mark) or start of range 2859 reference (text:reference-mark-start). 2860 2861 Arguments: 2862 2863 position -- int 2864 2865 name -- str 2866 2867 Return: Element or None if not found 2868 """ 2869 if name: 2870 request = ( 2871 f"descendant::text:reference-mark-start" 2872 f'[@text:name="{name}"] ' 2873 f"| descendant::text:reference-mark" 2874 f'[@text:name="{name}"]' 2875 ) 2876 return self._filtered_element(request, position=0) 2877 request = ( 2878 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2879 ) 2880 return self._filtered_element(request, position) 2881 2882 def get_references(self, name: str | None = None) -> list[Element]: 2883 """Return all the references (text:reference-ref). If name is 2884 provided, returns the references of that name. 2885 2886 Return: list of Element 2887 2888 Arguments: 2889 2890 name -- str or None 2891 """ 2892 if name is None: 2893 return self._filtered_elements("descendant::text:reference-ref") 2894 request = f'descendant::text:reference-ref[@text:ref-name="{name}"]' 2895 return self._filtered_elements(request) 2896 2897 # Shapes elements 2898 2899 # Groups 2900 2901 def get_draw_groups( 2902 self, 2903 title: str | None = None, 2904 description: str | None = None, 2905 content: str | None = None, 2906 ) -> list[Element]: 2907 return self._filtered_elements( 2908 "descendant::draw:g", 2909 svg_title=title, 2910 svg_desc=description, 2911 content=content, 2912 ) 2913 2914 def get_draw_group( 2915 self, 2916 position: int = 0, 2917 name: str | None = None, 2918 title: str | None = None, 2919 description: str | None = None, 2920 content: str | None = None, 2921 ) -> Element | None: 2922 return self._filtered_element( 2923 "descendant::draw:g", 2924 position, 2925 draw_name=name, 2926 svg_title=title, 2927 svg_desc=description, 2928 content=content, 2929 ) 2930 2931 # Lines 2932 2933 def get_draw_lines( 2934 self, 2935 draw_style: str | None = None, 2936 draw_text_style: str | None = None, 2937 content: str | None = None, 2938 ) -> list[Element]: 2939 """Return all the draw lines that match the criteria. 2940 2941 Arguments: 2942 2943 draw_style -- str 2944 2945 draw_text_style -- str 2946 2947 content -- str regex 2948 2949 Return: list of odf_shape 2950 """ 2951 return self._filtered_elements( 2952 "descendant::draw:line", 2953 draw_style=draw_style, 2954 draw_text_style=draw_text_style, 2955 content=content, 2956 ) 2957 2958 def get_draw_line( 2959 self, 2960 position: int = 0, 2961 id: str | None = None, # noqa:A002 2962 content: str | None = None, 2963 ) -> Element | None: 2964 """Return the draw line that matches the criteria. 2965 2966 Arguments: 2967 2968 position -- int 2969 2970 id -- str 2971 2972 content -- str regex 2973 2974 Return: odf_shape or None if not found 2975 """ 2976 return self._filtered_element( 2977 "descendant::draw:line", position, draw_id=id, content=content 2978 ) 2979 2980 # Rectangles 2981 2982 def get_draw_rectangles( 2983 self, 2984 draw_style: str | None = None, 2985 draw_text_style: str | None = None, 2986 content: str | None = None, 2987 ) -> list[Element]: 2988 """Return all the draw rectangles that match the criteria. 2989 2990 Arguments: 2991 2992 draw_style -- str 2993 2994 draw_text_style -- str 2995 2996 content -- str regex 2997 2998 Return: list of odf_shape 2999 """ 3000 return self._filtered_elements( 3001 "descendant::draw:rect", 3002 draw_style=draw_style, 3003 draw_text_style=draw_text_style, 3004 content=content, 3005 ) 3006 3007 def get_draw_rectangle( 3008 self, 3009 position: int = 0, 3010 id: str | None = None, # noqa:A002 3011 content: str | None = None, 3012 ) -> Element | None: 3013 """Return the draw rectangle that matches the criteria. 3014 3015 Arguments: 3016 3017 position -- int 3018 3019 id -- str 3020 3021 content -- str regex 3022 3023 Return: odf_shape or None if not found 3024 """ 3025 return self._filtered_element( 3026 "descendant::draw:rect", position, draw_id=id, content=content 3027 ) 3028 3029 # Ellipse 3030 3031 def get_draw_ellipses( 3032 self, 3033 draw_style: str | None = None, 3034 draw_text_style: str | None = None, 3035 content: str | None = None, 3036 ) -> list[Element]: 3037 """Return all the draw ellipses that match the criteria. 3038 3039 Arguments: 3040 3041 draw_style -- str 3042 3043 draw_text_style -- str 3044 3045 content -- str regex 3046 3047 Return: list of odf_shape 3048 """ 3049 return self._filtered_elements( 3050 "descendant::draw:ellipse", 3051 draw_style=draw_style, 3052 draw_text_style=draw_text_style, 3053 content=content, 3054 ) 3055 3056 def get_draw_ellipse( 3057 self, 3058 position: int = 0, 3059 id: str | None = None, # noqa:A002 3060 content: str | None = None, 3061 ) -> Element | None: 3062 """Return the draw ellipse that matches the criteria. 3063 3064 Arguments: 3065 3066 position -- int 3067 3068 id -- str 3069 3070 content -- str regex 3071 3072 Return: odf_shape or None if not found 3073 """ 3074 return self._filtered_element( 3075 "descendant::draw:ellipse", position, draw_id=id, content=content 3076 ) 3077 3078 # Connectors 3079 3080 def get_draw_connectors( 3081 self, 3082 draw_style: str | None = None, 3083 draw_text_style: str | None = None, 3084 content: str | None = None, 3085 ) -> list[Element]: 3086 """Return all the draw connectors that match the criteria. 3087 3088 Arguments: 3089 3090 draw_style -- str 3091 3092 draw_text_style -- str 3093 3094 content -- str regex 3095 3096 Return: list of odf_shape 3097 """ 3098 return self._filtered_elements( 3099 "descendant::draw:connector", 3100 draw_style=draw_style, 3101 draw_text_style=draw_text_style, 3102 content=content, 3103 ) 3104 3105 def get_draw_connector( 3106 self, 3107 position: int = 0, 3108 id: str | None = None, # noqa:A002 3109 content: str | None = None, 3110 ) -> Element | None: 3111 """Return the draw connector that matches the criteria. 3112 3113 Arguments: 3114 3115 position -- int 3116 3117 id -- str 3118 3119 content -- str regex 3120 3121 Return: odf_shape or None if not found 3122 """ 3123 return self._filtered_element( 3124 "descendant::draw:connector", position, draw_id=id, content=content 3125 ) 3126 3127 def get_orphan_draw_connectors(self) -> list[Element]: 3128 """Return a list of connectors which don't have any shape connected 3129 to them. 3130 """ 3131 connectors = [] 3132 for connector in self.get_draw_connectors(): 3133 start_shape = connector.get_attribute("draw:start-shape") 3134 end_shape = connector.get_attribute("draw:end-shape") 3135 if start_shape is None and end_shape is None: 3136 connectors.append(connector) 3137 return connectors 3138 3139 # Tracked changes and text change 3140 3141 def get_tracked_changes(self) -> Element | None: 3142 """Return the tracked-changes part in the text body.""" 3143 return self.get_element("//text:tracked-changes") 3144 3145 def get_changes_ids(self) -> list[Element | Text]: 3146 """Return a list of ids that refers to a change region in the tracked 3147 changes list. 3148 """ 3149 # Insertion changes 3150 xpath_query = "descendant::text:change-start/@text:change-id" 3151 # Deletion changes 3152 xpath_query += " | descendant::text:change/@text:change-id" 3153 return self.xpath(xpath_query) 3154 3155 def get_text_change_deletions(self) -> list[Element]: 3156 """Return all the text changes of deletion kind: the tags text:change. 3157 Consider using : get_text_changes() 3158 3159 Return: list of Element 3160 """ 3161 return self._filtered_elements("descendant::text:text:change") 3162 3163 def get_text_change_deletion( 3164 self, 3165 position: int = 0, 3166 idx: str | None = None, 3167 ) -> Element | None: 3168 """Return the text change of deletion kind that matches the criteria. 3169 Search only for the tags text:change. 3170 Consider using : get_text_change() 3171 3172 Arguments: 3173 3174 position -- int 3175 3176 idx -- str 3177 3178 Return: Element or None if not found 3179 """ 3180 return self._filtered_element( 3181 "descendant::text:change", position, change_id=idx 3182 ) 3183 3184 def get_text_change_starts(self) -> list[Element]: 3185 """Return all the text change-start. Search only for the tags 3186 text:change-start. 3187 Consider using : get_text_changes() 3188 3189 Return: list of Element 3190 """ 3191 return self._filtered_elements("descendant::text:change-start") 3192 3193 def get_text_change_start( 3194 self, 3195 position: int = 0, 3196 idx: str | None = None, 3197 ) -> Element | None: 3198 """Return the text change-start that matches the criteria. Search 3199 only the tags text:change-start. 3200 Consider using : get_text_change() 3201 3202 Arguments: 3203 3204 position -- int 3205 3206 idx -- str 3207 3208 Return: Element or None if not found 3209 """ 3210 return self._filtered_element( 3211 "descendant::text:change-start", position, change_id=idx 3212 ) 3213 3214 def get_text_change_ends(self) -> list[Element]: 3215 """Return all the text change-end. Search only the tags 3216 text:change-end. 3217 Consider using : get_text_changes() 3218 3219 Return: list of Element 3220 """ 3221 return self._filtered_elements("descendant::text:change-end") 3222 3223 def get_text_change_end( 3224 self, 3225 position: int = 0, 3226 idx: str | None = None, 3227 ) -> Element | None: 3228 """Return the text change-end that matches the criteria. Search only 3229 the tags text:change-end. 3230 Consider using : get_text_change() 3231 3232 Arguments: 3233 3234 position -- int 3235 3236 idx -- str 3237 3238 Return: Element or None if not found 3239 """ 3240 return self._filtered_element( 3241 "descendant::text:change-end", position, change_id=idx 3242 ) 3243 3244 def get_text_changes(self) -> list[Element]: 3245 """Return all the text changes, either single deletion 3246 (text:change) or start of range of changes (text:change-start). 3247 3248 Return: list of Element 3249 """ 3250 request = "descendant::text:change-start | descendant::text:change" 3251 return self._filtered_elements(request) 3252 3253 def get_text_change( 3254 self, 3255 position: int = 0, 3256 idx: str | None = None, 3257 ) -> Element | None: 3258 """Return the text change that matches the criteria. Either single 3259 deletion (text:change) or start of range of changes (text:change-start). 3260 position : index of the element to retrieve if several matches, default 3261 is 0. 3262 idx : change-id of the element. 3263 3264 Arguments: 3265 3266 position -- int 3267 3268 idx -- str 3269 3270 Return: Element or None if not found 3271 """ 3272 if idx: 3273 request = ( 3274 f'descendant::text:change-start[@text:change-id="{idx}"] ' 3275 f'| descendant::text:change[@text:change-id="{idx}"]' 3276 ) 3277 return self._filtered_element(request, 0) 3278 request = "descendant::text:change-start | descendant::text:change" 3279 return self._filtered_element(request, position) 3280 3281 # Table Of Content 3282 3283 def get_tocs(self) -> list[Element]: 3284 """Return all the tables of contents. 3285 3286 Return: list of odf_toc 3287 """ 3288 return self._filtered_elements("text:table-of-content") 3289 3290 def get_toc( 3291 self, 3292 position: int = 0, 3293 content: str | None = None, 3294 ) -> Element | None: 3295 """Return the table of contents that matches the criteria. 3296 3297 Arguments: 3298 3299 position -- int 3300 3301 content -- str regex 3302 3303 Return: odf_toc or None if not found 3304 """ 3305 return self._filtered_element( 3306 "text:table-of-content", position, content=content 3307 ) 3308 3309 # Styles 3310 3311 @staticmethod 3312 def _get_style_tagname(family: str | None, is_default: bool = False) -> str: 3313 """Widely match possible tag names given the family (or not).""" 3314 if not family: 3315 tagname = "(style:default-style|*[@style:name]|draw:fill-image|draw:marker)" 3316 elif is_default: 3317 # Default style 3318 tagname = "style:default-style" 3319 else: 3320 tagname = _family_style_tagname(family) 3321 # if famattr: 3322 # # Include family default style 3323 # tagname = '(%s|style:default-style)' % tagname 3324 if family in FAMILY_ODF_STD: 3325 # Include family default style 3326 tagname = f"({tagname}|style:default-style)" 3327 return tagname 3328 3329 def get_styles(self, family: str | None = None) -> list[Element]: 3330 # Both common and default styles 3331 tagname = self._get_style_tagname(family) 3332 return self._filtered_elements(tagname, family=family) 3333 3334 def get_style( 3335 self, 3336 family: str, 3337 name_or_element: str | Element | None = None, 3338 display_name: str | None = None, 3339 ) -> Element | None: 3340 """Return the style uniquely identified by the family/name pair. If 3341 the argument is already a style object, it will return it. 3342 3343 If the name is not the internal name but the name you gave in the 3344 desktop application, use display_name instead. 3345 3346 Arguments: 3347 3348 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 3349 'number' 3350 3351 name_or_element -- str or Style 3352 3353 display_name -- str 3354 3355 Return: odf_style or None if not found 3356 """ 3357 if isinstance(name_or_element, Element): 3358 name = self.get_attribute("style:name") 3359 if name is not None: 3360 return name_or_element 3361 else: 3362 raise ValueError(f"Not a odf_style ? {name_or_element!r}") 3363 style_name = name_or_element 3364 is_default = not (style_name or display_name) 3365 tagname = self._get_style_tagname(family, is_default=is_default) 3366 # famattr became None if no "style:family" attribute 3367 if family: 3368 return self._filtered_element( 3369 tagname, 3370 0, 3371 style_name=style_name, 3372 display_name=display_name, 3373 family=family, 3374 ) 3375 else: 3376 return self._filtered_element( 3377 tagname, 3378 0, 3379 draw_name=style_name or display_name, 3380 family=family, 3381 ) 3382 3383 def _filtered_element( 3384 self, 3385 query_string: str, 3386 position: int, 3387 **kwargs: Any, 3388 ) -> Element | None: 3389 results = self._filtered_elements(query_string, **kwargs) 3390 try: 3391 return results[position] 3392 except IndexError: 3393 return None 3394 3395 def _filtered_elements( 3396 self, 3397 query_string: str, 3398 content: str | None = None, 3399 url: str | None = None, 3400 svg_title: str | None = None, 3401 svg_desc: str | None = None, 3402 dc_creator: str | None = None, 3403 dc_date: datetime | None = None, 3404 **kwargs: Any, 3405 ) -> list[Element]: 3406 query = make_xpath_query(query_string, **kwargs) 3407 elements = self.get_elements(query) 3408 # Filter the elements with the regex (TODO use XPath) 3409 if content is not None: 3410 elements = [element for element in elements if element.match(content)] 3411 if url is not None: 3412 filtered = [] 3413 for element in elements: 3414 url_attr = element.get_attribute("xlink:href") 3415 if isinstance(url_attr, str) and search(url, url_attr) is not None: 3416 filtered.append(element) 3417 elements = filtered 3418 if dc_date is None: 3419 dt_dc_date = None 3420 else: 3421 dt_dc_date = DateTime.encode(dc_date) 3422 for variable, childname in [ 3423 (svg_title, "svg:title"), 3424 (svg_desc, "svg:desc"), 3425 (dc_creator, "descendant::dc:creator"), 3426 (dt_dc_date, "descendant::dc:date"), 3427 ]: 3428 if not variable: 3429 continue 3430 filtered = [] 3431 for element in elements: 3432 child = element.get_element(childname) 3433 if child and child.match(variable): 3434 filtered.append(element) 3435 elements = filtered 3436 return elements
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
314 def __init__(self, **kwargs: Any) -> None: 315 tag_or_elem = kwargs.pop("tag_or_elem", None) 316 if tag_or_elem is None: 317 # Instance for newly created object: create new lxml element and 318 # continue by subclass __init__ 319 # If the tag key word exists, make a custom element 320 self._do_init = True 321 tag = kwargs.pop("tag", self._tag) 322 self.__element = self.make_etree_element(tag) 323 else: 324 # called with an existing lxml element, sould be a result of 325 # from_tag() casting, do not execute the subclass __init__ 326 if not isinstance(tag_or_elem, _Element): 327 raise TypeError(f'"{type(tag_or_elem)}" is not an element node') 328 self._do_init = False 329 self.__element = tag_or_elem
337 @classmethod 338 def from_tag(cls, tag_or_elem: str | _Element) -> Element: 339 """Element class and subclass factory. 340 341 Turn an lxml Element or ODF string tag into an ODF XML Element 342 of the relevant class. 343 344 Arguments: 345 346 tag_or_elem -- ODF str tag or lxml.Element 347 348 Return: Element (or subclass) instance 349 """ 350 if isinstance(tag_or_elem, str): 351 # assume the argument is a prefix:name tag 352 elem = cls.make_etree_element(tag_or_elem) 353 else: 354 elem = tag_or_elem 355 klass = _class_registry.get(elem.tag, cls) 356 return klass(tag_or_elem=elem)
Element class and subclass factory.
Turn an lxml Element or ODF string tag into an ODF XML Element of the relevant class.
Arguments:
tag_or_elem -- ODF str tag or lxml.Element
Return: Element (or subclass) instance
358 @classmethod 359 def from_tag_for_clone( 360 cls: type, 361 tree_element: _Element, 362 cache: tuple | None, 363 ) -> Element: 364 tag = to_str(tree_element.tag) 365 klass = _class_registry.get(tag, cls) 366 element = klass(tag_or_elem=tree_element) 367 if cache and element._caching: 368 element._tmap = cache[0] 369 element._cmap = cache[1] 370 if len(cache) == 3: 371 element._rmap = cache[2] 372 return element
374 @staticmethod 375 def make_etree_element(tag: str) -> _Element: 376 if not isinstance(tag, str): 377 raise TypeError(f"Tag is not str: {tag!r}") 378 tag = tag.strip() 379 if not tag: 380 raise ValueError("Tag is empty") 381 if "<" not in tag: 382 # Qualified name 383 # XXX don't build the element from scratch or lxml will pollute with 384 # repeated namespace declarations 385 tag = f"<{tag}/>" 386 # XML fragment 387 root = fromstring(NAMESPACES_XML % str_to_bytes(tag)) 388 return root[0]
751 @property 752 def tag(self) -> str: 753 """Get/set the underlying xml tag with the given qualified name. 754 755 Warning: direct change of tag does not change the element class. 756 757 Arguments: 758 759 qname -- str (e.g. "text:span") 760 """ 761 return _get_prefixed_name(self.__element.tag)
Get/set the underlying xml tag with the given qualified name.
Warning: direct change of tag does not change the element class.
Arguments:
qname -- str (e.g. "text:span")
767 def elements_repeated_sequence( 768 self, 769 xpath_instance: XPath, 770 name: str, 771 ) -> list[tuple[int, int]]: 772 """Utility method for table module.""" 773 lxml_tag = _get_lxml_tag_or_name(name) 774 element = self.__element 775 sub_elements = xpath_instance(element) 776 if not isinstance(sub_elements, list): 777 raise TypeError("Bad XPath result.") 778 result: list[tuple[int, int]] = [] 779 idx = -1 780 for sub_element in sub_elements: 781 if not isinstance(sub_element, _Element): 782 continue 783 idx += 1 784 value = sub_element.get(lxml_tag) 785 if value is None: 786 result.append((idx, 1)) 787 continue 788 try: 789 int_value = int(value) 790 except ValueError: 791 int_value = 1 792 result.append((idx, max(int_value, 1))) 793 return result
Utility method for table module.
795 def get_elements(self, xpath_query: XPath | str) -> list[Element]: 796 cache: tuple | None = None 797 element = self.__element 798 if isinstance(xpath_query, str): 799 new_xpath_query = xpath_compile(xpath_query) 800 result = new_xpath_query(element) 801 else: 802 result = xpath_query(element) 803 if not isinstance(result, list): 804 raise TypeError("Bad XPath result") 805 806 if hasattr(self, "_tmap"): 807 if hasattr(self, "_rmap"): 808 cache = (self._tmap, self._cmap, self._rmap) 809 else: 810 cache = (self._tmap, self._cmap) 811 return [ 812 Element.from_tag_for_clone(e, cache) 813 for e in result 814 if isinstance(e, _Element) 815 ]
847 def get_attribute(self, name: str) -> str | bool | None: 848 """Return the attribute value as type str | bool | None.""" 849 element = self.__element 850 lxml_tag = _get_lxml_tag_or_name(name) 851 value = element.get(lxml_tag) 852 if value is None: 853 return None 854 elif value in ("true", "false"): 855 return Boolean.decode(value) 856 return str(value)
Return the attribute value as type str | bool | None.
858 def get_attribute_integer(self, name: str) -> int | None: 859 """Return either the attribute as type int, or None.""" 860 element = self.__element 861 lxml_tag = _get_lxml_tag_or_name(name) 862 value = element.get(lxml_tag) 863 if value is None: 864 return None 865 try: 866 return int(value) 867 except ValueError: 868 return None
Return either the attribute as type int, or None.
870 def get_attribute_string(self, name: str) -> str | None: 871 """Return either the attribute as type str, or None.""" 872 element = self.__element 873 lxml_tag = _get_lxml_tag_or_name(name) 874 value = element.get(lxml_tag) 875 if value is None: 876 return None 877 return str(value)
Return either the attribute as type str, or None.
879 def set_attribute(self, name: str, value: bool | str | None) -> None: 880 element = self.__element 881 lxml_tag = _get_lxml_tag_or_name(name) 882 if isinstance(value, bool): 883 value = Boolean.encode(value) 884 elif value is None: 885 with contextlib.suppress(KeyError): 886 del element.attrib[lxml_tag] 887 return 888 element.set(lxml_tag, str(value))
890 def set_style_attribute(self, name: str, value: Element | str) -> None: 891 """Shortcut to accept a style object as a value.""" 892 if isinstance(value, Element): 893 value = str(value.name) # type:ignore 894 return self.set_attribute(name, value)
Shortcut to accept a style object as a value.
901 @property 902 def text(self) -> str: 903 """Get / set the text content of the element.""" 904 return self.__element.text or ""
Get / set the text content of the element.
919 @property 920 def tail(self) -> str | None: 921 """Get / set the text immediately following the element.""" 922 return self.__element.tail
Get / set the text immediately following the element.
928 def search(self, pattern: str) -> int | None: 929 """Return the first position of the pattern in the text content of 930 the element, or None if not found. 931 932 Python regular expression syntax applies. 933 934 Arguments: 935 936 pattern -- str 937 938 Return: int or None 939 """ 940 match = re.search(pattern, self.text_recursive) 941 if match is None: 942 return None 943 return match.start()
Return the first position of the pattern in the text content of the element, or None if not found.
Python regular expression syntax applies.
Arguments:
pattern -- str
Return: int or None
945 def match(self, pattern: str) -> bool: 946 """return True if the pattern is found one or more times anywhere in 947 the text content of the element. 948 949 Python regular expression syntax applies. 950 951 Arguments: 952 953 pattern -- str 954 955 Return: bool 956 """ 957 return self.search(pattern) is not None
return True if the pattern is found one or more times anywhere in the text content of the element.
Python regular expression syntax applies.
Arguments:
pattern -- str
Return: bool
959 def replace(self, pattern: str, new: str | None = None) -> int: 960 """Replace the pattern with the given text, or delete if text is an 961 empty string, and return the number of replacements. By default, only 962 return the number of occurences that would be replaced. 963 964 It cannot replace patterns found across several element, like a word 965 split into two consecutive spans. 966 967 Python regular expression syntax applies. 968 969 Arguments: 970 971 pattern -- str 972 973 new -- str 974 975 Return: int 976 """ 977 if not isinstance(pattern, str): 978 # Fail properly if the pattern is an non-ascii bytestring 979 pattern = str(pattern) 980 cpattern = re.compile(pattern) 981 count = 0 982 for text in self.xpath("descendant::text()"): 983 if new is None: 984 count += len(cpattern.findall(str(text))) 985 else: 986 new_text, number = cpattern.subn(new, str(text)) 987 container = text.parent 988 if text.is_text(): # type: ignore 989 container.text = new_text # type: ignore 990 else: 991 container.tail = new_text # type: ignore 992 count += number 993 return count
Replace the pattern with the given text, or delete if text is an empty string, and return the number of replacements. By default, only return the number of occurences that would be replaced.
It cannot replace patterns found across several element, like a word split into two consecutive spans.
Python regular expression syntax applies.
Arguments:
pattern -- str
new -- str
Return: int
1038 def index(self, child: Element) -> int: 1039 """Return the position of the child in this element. 1040 1041 Inspired by lxml 1042 """ 1043 return self.__element.index(child.__element)
Return the position of the child in this element.
Inspired by lxml
1045 @property 1046 def text_content(self) -> str: 1047 """Get / set the text of the embedded paragraph, including embeded 1048 annotations, cells... 1049 1050 Set create a paragraph if missing 1051 """ 1052 return "\n".join( 1053 child.text_recursive for child in self.get_elements("descendant::text:p") 1054 )
Get / set the text of the embedded paragraph, including embeded annotations, cells...
Set create a paragraph if missing
1086 def is_empty(self) -> bool: 1087 """Check if the element is empty : no text, no children, no tail. 1088 1089 Return: Boolean 1090 """ 1091 element = self.__element 1092 if element.tail is not None: 1093 return False 1094 if element.text is not None: 1095 return False 1096 if list(element.iterchildren()): 1097 return False 1098 return True
Check if the element is empty : no text, no children, no tail.
Return: Boolean
1243 def get_between( 1244 self, 1245 tag1: Element, 1246 tag2: Element, 1247 as_text: bool = False, 1248 clean: bool = True, 1249 no_header: bool = True, 1250 ) -> list | Element | str: 1251 """Returns elements between tag1 and tag2, tag1 and tag2 shall 1252 be unique and having an id attribute. 1253 (WARN: buggy if tag1/tag2 defines a malformed odf xml.) 1254 If as_text is True: returns the text content. 1255 If clean is True: suppress unwanted tags (deletions marks, ...) 1256 If no_header is True: existing text:h are changed in text:p 1257 By default: returns a list of Element, cleaned and without headers. 1258 1259 Implementation and standard retrictions: 1260 Only text:h and text:p sould be 'cut' by an insert tag, so inner parts 1261 of insert tags are: 1262 1263 - any text:h, text:p or sub tag of these 1264 1265 - some text, part of a parent text:h or text:p 1266 1267 Arguments: 1268 1269 tag1 -- Element 1270 1271 tag2 -- Element 1272 1273 as_text -- boolean 1274 1275 clean -- boolean 1276 1277 no_header -- boolean 1278 1279 Return: list of odf_paragraph or odf_header 1280 """ 1281 inner = self._get_between_base(tag1, tag2) 1282 1283 if clean: 1284 clean_tags = ( 1285 "text:change", 1286 "text:change-start", 1287 "text:change-end", 1288 "text:reference-mark", 1289 "text:reference-mark-start", 1290 "text:reference-mark-end", 1291 ) 1292 request_self = " | ".join(["self::%s" % c for c in clean_tags]) 1293 inner = [e for e in inner if not e.xpath(request_self)] 1294 request = " | ".join([f"descendant::{tag}" for tag in clean_tags]) 1295 for element in inner: 1296 to_del = element.xpath(request) 1297 for elem in to_del: 1298 if isinstance(elem, Element): 1299 element.delete(elem) 1300 if no_header: # crude replace t:h by t:p 1301 new_inner = [] 1302 for element in inner: 1303 if element.tag == "text:h": 1304 children = element.children 1305 text = element.__element.text 1306 para = Element.from_tag("text:p") 1307 para.text = text or "" 1308 for c in children: 1309 para.append(c) 1310 new_inner.append(para) 1311 else: 1312 new_inner.append(element) 1313 inner = new_inner 1314 if as_text: 1315 return "\n".join([e.get_formatted_text() for e in inner]) 1316 else: 1317 return inner
Returns elements between tag1 and tag2, tag1 and tag2 shall be unique and having an id attribute. (WARN: buggy if tag1/tag2 defines a malformed odf xml.) If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and without headers.
Implementation and standard retrictions: Only text:h and text:p sould be 'cut' by an insert tag, so inner parts of insert tags are:
- any text:h, text:p or sub tag of these
- some text, part of a parent text:h or text:p
Arguments:
tag1 -- Element
tag2 -- Element
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list of odf_paragraph or odf_header
1319 def insert( 1320 self, 1321 element: Element, 1322 xmlposition: int | None = None, 1323 position: int | None = None, 1324 start: bool = False, 1325 ) -> None: 1326 """Insert an element relatively to ourself. 1327 1328 Insert either using DOM vocabulary or by numeric position. 1329 If text start is True, insert the element before any existing text. 1330 1331 Position start at 0. 1332 1333 Arguments: 1334 1335 element -- Element 1336 1337 xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING 1338 or PREV_SIBLING 1339 1340 start -- Boolean 1341 1342 position -- int 1343 """ 1344 # child_tag = element.tag 1345 current = self.__element 1346 _element = element.__element 1347 if start: 1348 text = current.text 1349 if text is not None: 1350 current.text = None 1351 tail = _element.tail 1352 if tail is None: 1353 tail = text 1354 else: 1355 tail = tail + text 1356 _element.tail = tail 1357 position = 0 1358 if position is not None: 1359 current.insert(position, _element) 1360 elif xmlposition is FIRST_CHILD: 1361 current.insert(0, _element) 1362 elif xmlposition is LAST_CHILD: 1363 current.append(_element) 1364 elif xmlposition is NEXT_SIBLING: 1365 parent = current.getparent() 1366 index = parent.index(current) # type: ignore 1367 parent.insert(index + 1, _element) # type: ignore 1368 elif xmlposition is PREV_SIBLING: 1369 parent = current.getparent() 1370 index = parent.index(current) # type: ignore 1371 parent.insert(index, _element) # type: ignore 1372 else: 1373 raise ValueError("(xml)position must be defined")
Insert an element relatively to ourself.
Insert either using DOM vocabulary or by numeric position. If text start is True, insert the element before any existing text.
Position start at 0.
Arguments:
element -- Element
xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
or PREV_SIBLING
start -- Boolean
position -- int
1375 def extend(self, odf_elements: Iterable[Element]) -> None: 1376 """Fast append elements at the end of ourself using extend.""" 1377 if odf_elements: 1378 current = self.__element 1379 elements = [element.__element for element in odf_elements] 1380 current.extend(elements)
Fast append elements at the end of ourself using extend.
1382 def append(self, str_or_element: str | Element) -> None: 1383 """Insert element or text in the last position.""" 1384 current = self.__element 1385 if isinstance(str_or_element, str): 1386 # Has children ? 1387 children = list(current.iterchildren()) 1388 if children: 1389 # Append to tail of the last child 1390 last_child = children[-1] 1391 text = last_child.tail 1392 text = text if text is not None else "" 1393 text += str_or_element 1394 last_child.tail = text 1395 else: 1396 # Append to text of the element 1397 text = current.text 1398 text = text if text is not None else "" 1399 text += str_or_element 1400 current.text = text 1401 elif isinstance(str_or_element, Element): 1402 current.append(str_or_element.__element) 1403 else: 1404 raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')
Insert element or text in the last position.
1406 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 1407 """Delete the given element from the XML tree. If no element is given, 1408 "self" is deleted. The XML library may allow to continue to use an 1409 element now "orphan" as long as you have a reference to it. 1410 1411 if keep_tail is True (default), the tail text is not erased. 1412 1413 Arguments: 1414 1415 child -- Element 1416 1417 keep_tail -- boolean (default to True), True for most usages. 1418 """ 1419 if child is None: 1420 parent = self.parent 1421 if parent is None: 1422 raise ValueError(f"Can't delete the root element\n{self.serialize()}") 1423 child = self 1424 else: 1425 parent = self 1426 if keep_tail and child.__element.tail is not None: 1427 current = child.__element 1428 tail = str(current.tail) 1429 current.tail = None 1430 prev = current.getprevious() 1431 if prev is not None: 1432 if prev.tail is None: 1433 prev.tail = tail 1434 else: 1435 prev.tail += tail 1436 else: 1437 if parent.__element.text is None: 1438 parent.__element.text = tail 1439 else: 1440 parent.__element.text += tail 1441 parent.__element.remove(child.__element)
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
if keep_tail is True (default), the tail text is not erased.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
1443 def replace_element(self, old_element: Element, new_element: Element) -> None: 1444 """Replaces in place a sub element with the element passed as second 1445 argument. 1446 1447 Warning : no clone for old element. 1448 """ 1449 current = self.__element 1450 current.replace(old_element.__element, new_element.__element)
Replaces in place a sub element with the element passed as second argument.
Warning : no clone for old element.
1452 def strip_elements( 1453 self, 1454 sub_elements: Element | Iterable[Element], 1455 ) -> Element | list: 1456 """Remove the tags of provided elements, keeping inner childs and text. 1457 1458 Return : the striped element. 1459 1460 Warning : no clone in sub_elements list. 1461 1462 Arguments: 1463 1464 sub_elements -- Element or list of Element 1465 """ 1466 if not sub_elements: 1467 return self 1468 if isinstance(sub_elements, Element): 1469 sub_elements = (sub_elements,) 1470 replacer = _get_lxml_tag("text:this-will-be-removed") 1471 for element in sub_elements: 1472 element.__element.tag = replacer 1473 strip = ("text:this-will-be-removed",) 1474 return self.strip_tags(strip=strip, default=None)
Remove the tags of provided elements, keeping inner childs and text.
Return : the striped element.
Warning : no clone in sub_elements list.
Arguments:
sub_elements -- Element or list of Element
1574 def xpath(self, xpath_query: str) -> list[Element | Text]: 1575 """Apply XPath query to the element and its subtree. Return list of 1576 Element or Text instances translated from the nodes found. 1577 """ 1578 element = self.__element 1579 xpath_instance = xpath_compile(xpath_query) 1580 elements = xpath_instance(element) 1581 result: list[Element | Text] = [] 1582 if hasattr(elements, "__iter__"): 1583 for obj in elements: # type: ignore 1584 if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)): 1585 result.append(Text(obj)) 1586 elif isinstance(obj, _Element): 1587 result.append(Element.from_tag(obj)) 1588 # else: 1589 # result.append(obj) 1590 return result
Apply XPath query to the element and its subtree. Return list of Element or Text instances translated from the nodes found.
1592 def clear(self) -> None: 1593 """Remove text, children and attributes from the element.""" 1594 self.__element.clear() 1595 if hasattr(self, "_tmap"): 1596 self._tmap: list[int] = [] 1597 if hasattr(self, "_cmap"): 1598 self._cmap: list[int] = [] 1599 if hasattr(self, "_rmap"): 1600 self._rmap: list[int] = [] 1601 if hasattr(self, "_indexes"): 1602 remember = False 1603 if "_rmap" in self._indexes: 1604 remember = True 1605 self._indexes: dict[str, dict] = {} 1606 self._indexes["_cmap"] = {} 1607 self._indexes["_tmap"] = {} 1608 if remember: 1609 self._indexes["_rmap"] = {}
Remove text, children and attributes from the element.
1611 @property 1612 def clone(self) -> Element: 1613 clone = deepcopy(self.__element) 1614 root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES) 1615 root.append(clone) 1616 return self.from_tag(clone) 1617 1618 # slow data = tostring(self.__element, encoding='unicode') 1619 # return self.from_tag(data)
1626 def serialize(self, pretty: bool = False, with_ns: bool = False) -> str: 1627 """Return text serialization of XML element.""" 1628 # This copy bypasses serialization side-effects in lxml 1629 native = deepcopy(self.__element) 1630 data = tostring( 1631 native, with_tail=False, pretty_print=pretty, encoding="unicode" 1632 ) 1633 if with_ns: 1634 return data 1635 # Remove namespaces 1636 return self._strip_namespaces(data)
Return text serialization of XML element.
1640 @property 1641 def document_body(self) -> Element | None: 1642 """Return the document body : 'office:body'""" 1643 return self.get_element("//office:body/*[1]")
Return the document body : 'office:body'
1658 def get_formatted_text(self, context: dict | None = None) -> str: 1659 """This function should return a beautiful version of the text.""" 1660 return ""
This function should return a beautiful version of the text.
1662 def get_styled_elements(self, name: str = "") -> list[Element]: 1663 """Brute-force to find paragraphs, tables, etc. using the given style 1664 name (or all by default). 1665 1666 Arguments: 1667 1668 name -- str 1669 1670 Return: list 1671 """ 1672 # FIXME incomplete (and possibly inaccurate) 1673 return ( 1674 self._filtered_elements("descendant::*", text_style=name) 1675 + self._filtered_elements("descendant::*", draw_style=name) 1676 + self._filtered_elements("descendant::*", draw_text_style=name) 1677 + self._filtered_elements("descendant::*", table_style=name) 1678 + self._filtered_elements("descendant::*", page_layout=name) 1679 + self._filtered_elements("descendant::*", master_page=name) 1680 + self._filtered_elements("descendant::*", parent_style=name) 1681 )
Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).
Arguments:
name -- str
Return: list
1700 @property 1701 def dc_creator(self) -> str | None: 1702 """Get dc:creator value. 1703 1704 Return: str (or None if inexistant) 1705 """ 1706 return self._get_inner_text("dc:creator")
Get dc:creator value.
Return: str (or None if inexistant)
1718 @property 1719 def dc_date(self) -> datetime | None: 1720 """Get the dc:date value. 1721 1722 Return: datetime (or None if inexistant) 1723 """ 1724 date = self._get_inner_text("dc:date") 1725 if date is None: 1726 return None 1727 return DateTime.decode(date)
Get the dc:date value.
Return: datetime (or None if inexistant)
1759 def get_sections( 1760 self, 1761 style: str | None = None, 1762 content: str | None = None, 1763 ) -> list[Element]: 1764 """Return all the sections that match the criteria. 1765 1766 Arguments: 1767 1768 style -- str 1769 1770 content -- str regex 1771 1772 Return: list of Element 1773 """ 1774 return self._filtered_elements( 1775 "text:section", text_style=style, content=content 1776 )
Return all the sections that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Element
1778 def get_section( 1779 self, 1780 position: int = 0, 1781 content: str | None = None, 1782 ) -> Element | None: 1783 """Return the section that matches the criteria. 1784 1785 Arguments: 1786 1787 position -- int 1788 1789 content -- str regex 1790 1791 Return: Element or None if not found 1792 """ 1793 return self._filtered_element( 1794 "descendant::text:section", position, content=content 1795 )
Return the section that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Element or None if not found
1799 def get_paragraphs( 1800 self, 1801 style: str | None = None, 1802 content: str | None = None, 1803 ) -> list[Element]: 1804 """Return all the paragraphs that match the criteria. 1805 1806 Arguments: 1807 1808 style -- str 1809 1810 content -- str regex 1811 1812 Return: list of Paragraph 1813 """ 1814 return self._filtered_elements( 1815 "descendant::text:p", text_style=style, content=content 1816 )
Return all the paragraphs that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Paragraph
1818 def get_paragraph( 1819 self, 1820 position: int = 0, 1821 content: str | None = None, 1822 ) -> Element | None: 1823 """Return the paragraph that matches the criteria. 1824 1825 Arguments: 1826 1827 position -- int 1828 1829 content -- str regex 1830 1831 Return: Paragraph or None if not found 1832 """ 1833 return self._filtered_element("descendant::text:p", position, content=content)
Return the paragraph that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Paragraph or None if not found
1837 def get_spans( 1838 self, 1839 style: str | None = None, 1840 content: str | None = None, 1841 ) -> list[Element]: 1842 """Return all the spans that match the criteria. 1843 1844 Arguments: 1845 1846 style -- str 1847 1848 content -- str regex 1849 1850 Return: list of Span 1851 """ 1852 return self._filtered_elements( 1853 "descendant::text:span", text_style=style, content=content 1854 )
Return all the spans that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Span
1856 def get_span( 1857 self, 1858 position: int = 0, 1859 content: str | None = None, 1860 ) -> Element | None: 1861 """Return the span that matches the criteria. 1862 1863 Arguments: 1864 1865 position -- int 1866 1867 content -- str regex 1868 1869 Return: Span or None if not found 1870 """ 1871 return self._filtered_element( 1872 "descendant::text:span", position, content=content 1873 )
Return the span that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Span or None if not found
1877 def get_headers( 1878 self, 1879 style: str | None = None, 1880 outline_level: str | None = None, 1881 content: str | None = None, 1882 ) -> list[Element]: 1883 """Return all the Headers that match the criteria. 1884 1885 Arguments: 1886 1887 style -- str 1888 1889 content -- str regex 1890 1891 Return: list of Header 1892 """ 1893 return self._filtered_elements( 1894 "descendant::text:h", 1895 text_style=style, 1896 outline_level=outline_level, 1897 content=content, 1898 )
Return all the Headers that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Header
1900 def get_header( 1901 self, 1902 position: int = 0, 1903 outline_level: str | None = None, 1904 content: str | None = None, 1905 ) -> Element | None: 1906 """Return the Header that matches the criteria. 1907 1908 Arguments: 1909 1910 position -- int 1911 1912 content -- str regex 1913 1914 Return: Header or None if not found 1915 """ 1916 return self._filtered_element( 1917 "descendant::text:h", 1918 position, 1919 outline_level=outline_level, 1920 content=content, 1921 )
Return the Header that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Header or None if not found
1925 def get_lists( 1926 self, 1927 style: str | None = None, 1928 content: str | None = None, 1929 ) -> list[Element]: 1930 """Return all the lists that match the criteria. 1931 1932 Arguments: 1933 1934 style -- str 1935 1936 content -- str regex 1937 1938 Return: list of List 1939 """ 1940 return self._filtered_elements( 1941 "descendant::text:list", text_style=style, content=content 1942 )
Return all the lists that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of List
1944 def get_list( 1945 self, 1946 position: int = 0, 1947 content: str | None = None, 1948 ) -> Element | None: 1949 """Return the list that matches the criteria. 1950 1951 Arguments: 1952 1953 position -- int 1954 1955 content -- str regex 1956 1957 Return: List or None if not found 1958 """ 1959 return self._filtered_element( 1960 "descendant::text:list", position, content=content 1961 )
Return the list that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: List or None if not found
1965 def get_frames( 1966 self, 1967 presentation_class: str | None = None, 1968 style: str | None = None, 1969 title: str | None = None, 1970 description: str | None = None, 1971 content: str | None = None, 1972 ) -> list[Element]: 1973 """Return all the frames that match the criteria. 1974 1975 Arguments: 1976 1977 presentation_class -- str 1978 1979 style -- str 1980 1981 title -- str regex 1982 1983 description -- str regex 1984 1985 content -- str regex 1986 1987 Return: list of Frame 1988 """ 1989 return self._filtered_elements( 1990 "descendant::draw:frame", 1991 presentation_class=presentation_class, 1992 draw_style=style, 1993 svg_title=title, 1994 svg_desc=description, 1995 content=content, 1996 )
Return all the frames that match the criteria.
Arguments:
presentation_class -- str
style -- str
title -- str regex
description -- str regex
content -- str regex
Return: list of Frame
1998 def get_frame( 1999 self, 2000 position: int = 0, 2001 name: str | None = None, 2002 presentation_class: str | None = None, 2003 title: str | None = None, 2004 description: str | None = None, 2005 content: str | None = None, 2006 ) -> Element | None: 2007 """Return the section that matches the criteria. 2008 2009 Arguments: 2010 2011 position -- int 2012 2013 name -- str 2014 2015 presentation_class -- str 2016 2017 title -- str regex 2018 2019 description -- str regex 2020 2021 content -- str regex 2022 2023 Return: Frame or None if not found 2024 """ 2025 return self._filtered_element( 2026 "descendant::draw:frame", 2027 position, 2028 draw_name=name, 2029 presentation_class=presentation_class, 2030 svg_title=title, 2031 svg_desc=description, 2032 content=content, 2033 )
Return the section that matches the criteria.
Arguments:
position -- int
name -- str
presentation_class -- str
title -- str regex
description -- str regex
content -- str regex
Return: Frame or None if not found
2037 def get_images( 2038 self, 2039 style: str | None = None, 2040 url: str | None = None, 2041 content: str | None = None, 2042 ) -> list[Element]: 2043 """Return all the images matching the criteria. 2044 2045 Arguments: 2046 2047 style -- str 2048 2049 url -- str regex 2050 2051 content -- str regex 2052 2053 Return: list of Element 2054 """ 2055 return self._filtered_elements( 2056 "descendant::draw:image", text_style=style, url=url, content=content 2057 )
Return all the images matching the criteria.
Arguments:
style -- str
url -- str regex
content -- str regex
Return: list of Element
2059 def get_image( 2060 self, 2061 position: int = 0, 2062 name: str | None = None, 2063 url: str | None = None, 2064 content: str | None = None, 2065 ) -> Element | None: 2066 """Return the image matching the criteria. 2067 2068 Arguments: 2069 2070 position -- int 2071 2072 name -- str 2073 2074 url -- str regex 2075 2076 content -- str regex 2077 2078 Return: Element or None if not found 2079 """ 2080 # The frame is holding the name 2081 if name is not None: 2082 frame = self._filtered_element( 2083 "descendant::draw:frame", position, draw_name=name 2084 ) 2085 if frame is None: 2086 return None 2087 # The name is supposedly unique 2088 return frame.get_element("draw:image") 2089 return self._filtered_element( 2090 "descendant::draw:image", position, url=url, content=content 2091 )
Return the image matching the criteria.
Arguments:
position -- int
name -- str
url -- str regex
content -- str regex
Return: Element or None if not found
2095 def get_tables( 2096 self, 2097 style: str | None = None, 2098 content: str | None = None, 2099 ) -> list[Element]: 2100 """Return all the tables that match the criteria. 2101 2102 Arguments: 2103 2104 style -- str 2105 2106 content -- str regex 2107 2108 Return: list of Table 2109 """ 2110 return self._filtered_elements( 2111 "descendant::table:table", table_style=style, content=content 2112 )
Return all the tables that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Table
2114 def get_table( 2115 self, 2116 position: int = 0, 2117 name: str | None = None, 2118 content: str | None = None, 2119 ) -> Element | None: 2120 """Return the table that matches the criteria. 2121 2122 Arguments: 2123 2124 position -- int 2125 2126 name -- str 2127 2128 content -- str regex 2129 2130 Return: Table or None if not found 2131 """ 2132 if name is None and content is None: 2133 result = self._filtered_element("descendant::table:table", position) 2134 else: 2135 result = self._filtered_element( 2136 "descendant::table:table", 2137 position, 2138 table_name=name, 2139 content=content, 2140 ) 2141 return result
Return the table that matches the criteria.
Arguments:
position -- int
name -- str
content -- str regex
Return: Table or None if not found
2145 def get_named_ranges(self) -> list[Element]: 2146 """Return all the tables named ranges. 2147 2148 Return: list of odf_named_range 2149 """ 2150 named_ranges = self.get_elements( 2151 "descendant::table:named-expressions/table:named-range" 2152 ) 2153 return named_ranges
Return all the tables named ranges.
Return: list of odf_named_range
2155 def get_named_range(self, name: str) -> Element | None: 2156 """Return the named range of specified name, or None if not found. 2157 2158 Arguments: 2159 2160 name -- str 2161 2162 Return: NamedRange 2163 """ 2164 named_range = self.get_elements( 2165 f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]' 2166 ) 2167 if named_range: 2168 return named_range[0] 2169 else: 2170 return None
Return the named range of specified name, or None if not found.
Arguments:
name -- str
Return: NamedRange
2172 def append_named_range(self, named_range: Element) -> None: 2173 """Append the named range to the spreadsheet, replacing existing named 2174 range of same name if any. 2175 2176 Arguments: 2177 2178 named_range -- NamedRange 2179 """ 2180 if self.tag != "office:spreadsheet": 2181 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2182 named_expressions = self.get_element("table:named-expressions") 2183 if not named_expressions: 2184 named_expressions = Element.from_tag("table:named-expressions") 2185 self.append(named_expressions) 2186 # exists ? 2187 current = named_expressions.get_element( 2188 f'table:named-range[@table:name="{named_range.name}"][1]' # type:ignore 2189 ) 2190 if current: 2191 named_expressions.delete(current) 2192 named_expressions.append(named_range)
Append the named range to the spreadsheet, replacing existing named range of same name if any.
Arguments:
named_range -- NamedRange
2194 def delete_named_range(self, name: str) -> None: 2195 """Delete the Named Range of specified name from the spreadsheet. 2196 2197 Arguments: 2198 2199 name -- str 2200 """ 2201 if self.tag != "office:spreadsheet": 2202 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2203 named_range = self.get_named_range(name) 2204 if not named_range: 2205 return 2206 named_range.delete() 2207 named_expressions = self.get_element("table:named-expressions") 2208 if not named_expressions: 2209 return 2210 element = named_expressions.__element 2211 children = list(element.iterchildren()) 2212 if not children: 2213 self.delete(named_expressions)
Delete the Named Range of specified name from the spreadsheet.
Arguments:
name -- str
2217 def get_notes( 2218 self, 2219 note_class: str | None = None, 2220 content: str | None = None, 2221 ) -> list[Element]: 2222 """Return all the notes that match the criteria. 2223 2224 Arguments: 2225 2226 note_class -- 'footnote' or 'endnote' 2227 2228 content -- str regex 2229 2230 Return: list of Note 2231 """ 2232 return self._filtered_elements( 2233 "descendant::text:note", note_class=note_class, content=content 2234 )
Return all the notes that match the criteria.
Arguments:
note_class -- 'footnote' or 'endnote'
content -- str regex
Return: list of Note
2236 def get_note( 2237 self, 2238 position: int = 0, 2239 note_id: str | None = None, 2240 note_class: str | None = None, 2241 content: str | None = None, 2242 ) -> Element | None: 2243 """Return the note that matches the criteria. 2244 2245 Arguments: 2246 2247 position -- int 2248 2249 note_id -- str 2250 2251 note_class -- 'footnote' or 'endnote' 2252 2253 content -- str regex 2254 2255 Return: Note or None if not found 2256 """ 2257 return self._filtered_element( 2258 "descendant::text:note", 2259 position, 2260 text_id=note_id, 2261 note_class=note_class, 2262 content=content, 2263 )
Return the note that matches the criteria.
Arguments:
position -- int
note_id -- str
note_class -- 'footnote' or 'endnote'
content -- str regex
Return: Note or None if not found
2267 def get_annotations( 2268 self, 2269 creator: str | None = None, 2270 start_date: datetime | None = None, 2271 end_date: datetime | None = None, 2272 content: str | None = None, 2273 ) -> list[Element]: 2274 """Return all the annotations that match the criteria. 2275 2276 Arguments: 2277 2278 creator -- str 2279 2280 start_date -- datetime instance 2281 2282 end_date -- datetime instance 2283 2284 content -- str regex 2285 2286 Return: list of Annotation 2287 """ 2288 annotations = [] 2289 for annotation in self._filtered_elements( 2290 "descendant::office:annotation", content=content 2291 ): 2292 if creator is not None and creator != annotation.dc_creator: 2293 continue 2294 date = annotation.dc_date 2295 if date is None: 2296 continue 2297 if start_date is not None and date < start_date: 2298 continue 2299 if end_date is not None and date >= end_date: 2300 continue 2301 annotations.append(annotation) 2302 return annotations
Return all the annotations that match the criteria.
Arguments:
creator -- str
start_date -- datetime instance
end_date -- datetime instance
content -- str regex
Return: list of Annotation
2304 def get_annotation( 2305 self, 2306 position: int = 0, 2307 creator: str | None = None, 2308 start_date: datetime | None = None, 2309 end_date: datetime | None = None, 2310 content: str | None = None, 2311 name: str | None = None, 2312 ) -> Element | None: 2313 """Return the annotation that matches the criteria. 2314 2315 Arguments: 2316 2317 position -- int 2318 2319 creator -- str 2320 2321 start_date -- datetime instance 2322 2323 end_date -- datetime instance 2324 2325 content -- str regex 2326 2327 name -- str 2328 2329 Return: Annotation or None if not found 2330 """ 2331 if name is not None: 2332 return self._filtered_element( 2333 "descendant::office:annotation", 0, office_name=name 2334 ) 2335 annotations = self.get_annotations( 2336 creator=creator, start_date=start_date, end_date=end_date, content=content 2337 ) 2338 if not annotations: 2339 return None 2340 try: 2341 return annotations[position] 2342 except IndexError: 2343 return None
Return the annotation that matches the criteria.
Arguments:
position -- int
creator -- str
start_date -- datetime instance
end_date -- datetime instance
content -- str regex
name -- str
Return: Annotation or None if not found
2345 def get_annotation_ends(self) -> list[Element]: 2346 """Return all the annotation ends. 2347 2348 Return: list of Element 2349 """ 2350 return self._filtered_elements("descendant::office:annotation-end")
Return all the annotation ends.
Return: list of Element
2352 def get_annotation_end( 2353 self, 2354 position: int = 0, 2355 name: str | None = None, 2356 ) -> Element | None: 2357 """Return the annotation end that matches the criteria. 2358 2359 Arguments: 2360 2361 position -- int 2362 2363 name -- str 2364 2365 Return: Element or None if not found 2366 """ 2367 return self._filtered_element( 2368 "descendant::office:annotation-end", position, office_name=name 2369 )
Return the annotation end that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2373 def get_office_names(self) -> list[str]: 2374 """Return all the used office:name tags values of the element. 2375 2376 Return: list of unique str 2377 """ 2378 name_xpath_query = xpath_compile("//@office:name") 2379 response = name_xpath_query(self.__element) 2380 if not isinstance(response, list): 2381 return [] 2382 return list({str(name) for name in response if name})
Return all the used office:name tags values of the element.
Return: list of unique str
2386 def get_variable_decls(self) -> Element: 2387 """Return the container for variable declarations. Created if not 2388 found. 2389 2390 Return: Element 2391 """ 2392 variable_decls = self.get_element("//text:variable-decls") 2393 if variable_decls is None: 2394 body = self.document_body 2395 if not body: 2396 raise ValueError("Empty document.body") 2397 body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD) 2398 variable_decls = body.get_element("//text:variable-decls") 2399 2400 return variable_decls # type:ignore
Return the container for variable declarations. Created if not found.
Return: Element
2402 def get_variable_decl_list(self) -> list[Element]: 2403 """Return all the variable declarations. 2404 2405 Return: list of Element 2406 """ 2407 return self._filtered_elements("descendant::text:variable-decl")
Return all the variable declarations.
Return: list of Element
2409 def get_variable_decl(self, name: str, position: int = 0) -> Element | None: 2410 """return the variable declaration for the given name. 2411 2412 Arguments: 2413 2414 name -- str 2415 2416 position -- int 2417 2418 return: Element or none if not found 2419 """ 2420 return self._filtered_element( 2421 "descendant::text:variable-decl", position, text_name=name 2422 )
return the variable declaration for the given name.
Arguments:
name -- str
position -- int
return: Element or none if not found
2424 def get_variable_sets(self, name: str | None = None) -> list[Element]: 2425 """Return all the variable sets that match the criteria. 2426 2427 Arguments: 2428 2429 name -- str 2430 2431 Return: list of Element 2432 """ 2433 return self._filtered_elements("descendant::text:variable-set", text_name=name)
Return all the variable sets that match the criteria.
Arguments:
name -- str
Return: list of Element
2435 def get_variable_set(self, name: str, position: int = -1) -> Element | None: 2436 """Return the variable set for the given name (last one by default). 2437 2438 Arguments: 2439 2440 name -- str 2441 2442 position -- int 2443 2444 Return: Element or None if not found 2445 """ 2446 return self._filtered_element( 2447 "descendant::text:variable-set", position, text_name=name 2448 )
Return the variable set for the given name (last one by default).
Arguments:
name -- str
position -- int
Return: Element or None if not found
2450 def get_variable_set_value( 2451 self, 2452 name: str, 2453 value_type: str | None = None, 2454 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2455 """Return the last value of the given variable name. 2456 2457 Arguments: 2458 2459 name -- str 2460 2461 value_type -- 'boolean', 'currency', 'date', 'float', 2462 'percentage', 'string', 'time' or automatic 2463 2464 Return: most appropriate Python type 2465 """ 2466 variable_set = self.get_variable_set(name) 2467 if not variable_set: 2468 return None 2469 return variable_set.get_value(value_type) # type: ignore
Return the last value of the given variable name.
Arguments:
name -- str
value_type -- 'boolean', 'currency', 'date', 'float',
'percentage', 'string', 'time' or automatic
Return: most appropriate Python type
2473 def get_user_field_decls(self) -> Element | None: 2474 """Return the container for user field declarations. Created if not 2475 found. 2476 2477 Return: Element 2478 """ 2479 user_field_decls = self.get_element("//text:user-field-decls") 2480 if user_field_decls is None: 2481 body = self.document_body 2482 if not body: 2483 raise ValueError("Empty document.body") 2484 body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD) 2485 user_field_decls = body.get_element("//text:user-field-decls") 2486 2487 return user_field_decls
Return the container for user field declarations. Created if not found.
Return: Element
2489 def get_user_field_decl_list(self) -> list[Element]: 2490 """Return all the user field declarations. 2491 2492 Return: list of Element 2493 """ 2494 return self._filtered_elements("descendant::text:user-field-decl")
Return all the user field declarations.
Return: list of Element
2496 def get_user_field_decl(self, name: str, position: int = 0) -> Element | None: 2497 """return the user field declaration for the given name. 2498 2499 return: Element or none if not found 2500 """ 2501 return self._filtered_element( 2502 "descendant::text:user-field-decl", position, text_name=name 2503 )
return the user field declaration for the given name.
return: Element or none if not found
2505 def get_user_field_value( 2506 self, name: str, value_type: str | None = None 2507 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2508 """Return the value of the given user field name. 2509 2510 Arguments: 2511 2512 name -- str 2513 2514 value_type -- 'boolean', 'currency', 'date', 'float', 2515 'percentage', 'string', 'time' or automatic 2516 2517 Return: most appropriate Python type 2518 """ 2519 user_field_decl = self.get_user_field_decl(name) 2520 if user_field_decl is None: 2521 return None 2522 return user_field_decl.get_value(value_type) # type: ignore
Return the value of the given user field name.
Arguments:
name -- str
value_type -- 'boolean', 'currency', 'date', 'float',
'percentage', 'string', 'time' or automatic
Return: most appropriate Python type
2527 def get_user_defined_list(self) -> list[Element]: 2528 """Return all the user defined field declarations. 2529 2530 Return: list of Element 2531 """ 2532 return self._filtered_elements("descendant::text:user-defined")
Return all the user defined field declarations.
Return: list of Element
2534 def get_user_defined(self, name: str, position: int = 0) -> Element | None: 2535 """return the user defined declaration for the given name. 2536 2537 return: Element or none if not found 2538 """ 2539 return self._filtered_element( 2540 "descendant::text:user-defined", position, text_name=name 2541 )
return the user defined declaration for the given name.
return: Element or none if not found
2543 def get_user_defined_value( 2544 self, name: str, value_type: str | None = None 2545 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2546 """Return the value of the given user defined field name. 2547 2548 Arguments: 2549 2550 name -- str 2551 2552 value_type -- 'boolean', 'date', 'float', 2553 'string', 'time' or automatic 2554 2555 Return: most appropriate Python type 2556 """ 2557 user_defined = self.get_user_defined(name) 2558 if user_defined is None: 2559 return None 2560 return user_defined.get_value(value_type) # type: ignore
Return the value of the given user defined field name.
Arguments:
name -- str
value_type -- 'boolean', 'date', 'float',
'string', 'time' or automatic
Return: most appropriate Python type
2564 def get_draw_pages( 2565 self, 2566 style: str | None = None, 2567 content: str | None = None, 2568 ) -> list[Element]: 2569 """Return all the draw pages that match the criteria. 2570 2571 Arguments: 2572 2573 style -- str 2574 2575 content -- str regex 2576 2577 Return: list of DrawPage 2578 """ 2579 return self._filtered_elements( 2580 "descendant::draw:page", draw_style=style, content=content 2581 )
Return all the draw pages that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of DrawPage
2583 def get_draw_page( 2584 self, 2585 position: int = 0, 2586 name: str | None = None, 2587 content: str | None = None, 2588 ) -> Element | None: 2589 """Return the draw page that matches the criteria. 2590 2591 Arguments: 2592 2593 position -- int 2594 2595 name -- str 2596 2597 content -- str regex 2598 2599 Return: DrawPage or None if not found 2600 """ 2601 return self._filtered_element( 2602 "descendant::draw:page", position, draw_name=name, content=content 2603 )
Return the draw page that matches the criteria.
Arguments:
position -- int
name -- str
content -- str regex
Return: DrawPage or None if not found
2607 def get_links( 2608 self, 2609 name: str | None = None, 2610 title: str | None = None, 2611 url: str | None = None, 2612 content: str | None = None, 2613 ) -> list[Element]: 2614 """Return all the links that match the criteria. 2615 2616 Arguments: 2617 2618 name -- str 2619 2620 title -- str 2621 2622 url -- str regex 2623 2624 content -- str regex 2625 2626 Return: list of Element 2627 """ 2628 return self._filtered_elements( 2629 "descendant::text:a", 2630 office_name=name, 2631 office_title=title, 2632 url=url, 2633 content=content, 2634 )
Return all the links that match the criteria.
Arguments:
name -- str
title -- str
url -- str regex
content -- str regex
Return: list of Element
2636 def get_link( 2637 self, 2638 position: int = 0, 2639 name: str | None = None, 2640 title: str | None = None, 2641 url: str | None = None, 2642 content: str | None = None, 2643 ) -> Element | None: 2644 """Return the link that matches the criteria. 2645 2646 Arguments: 2647 2648 position -- int 2649 2650 name -- str 2651 2652 title -- str 2653 2654 url -- str regex 2655 2656 content -- str regex 2657 2658 Return: Element or None if not found 2659 """ 2660 return self._filtered_element( 2661 "descendant::text:a", 2662 position, 2663 office_name=name, 2664 office_title=title, 2665 url=url, 2666 content=content, 2667 )
Return the link that matches the criteria.
Arguments:
position -- int
name -- str
title -- str
url -- str regex
content -- str regex
Return: Element or None if not found
2671 def get_bookmarks(self) -> list[Element]: 2672 """Return all the bookmarks. 2673 2674 Return: list of Element 2675 """ 2676 return self._filtered_elements("descendant::text:bookmark")
Return all the bookmarks.
Return: list of Element
2678 def get_bookmark( 2679 self, 2680 position: int = 0, 2681 name: str | None = None, 2682 ) -> Element | None: 2683 """Return the bookmark that matches the criteria. 2684 2685 Arguments: 2686 2687 position -- int 2688 2689 name -- str 2690 2691 Return: Bookmark or None if not found 2692 """ 2693 return self._filtered_element( 2694 "descendant::text:bookmark", position, text_name=name 2695 )
Return the bookmark that matches the criteria.
Arguments:
position -- int
name -- str
Return: Bookmark or None if not found
2697 def get_bookmark_starts(self) -> list[Element]: 2698 """Return all the bookmark starts. 2699 2700 Return: list of Element 2701 """ 2702 return self._filtered_elements("descendant::text:bookmark-start")
Return all the bookmark starts.
Return: list of Element
2704 def get_bookmark_start( 2705 self, 2706 position: int = 0, 2707 name: str | None = None, 2708 ) -> Element | None: 2709 """Return the bookmark start that matches the criteria. 2710 2711 Arguments: 2712 2713 position -- int 2714 2715 name -- str 2716 2717 Return: Element or None if not found 2718 """ 2719 return self._filtered_element( 2720 "descendant::text:bookmark-start", position, text_name=name 2721 )
Return the bookmark start that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2723 def get_bookmark_ends(self) -> list[Element]: 2724 """Return all the bookmark ends. 2725 2726 Return: list of Element 2727 """ 2728 return self._filtered_elements("descendant::text:bookmark-end")
Return all the bookmark ends.
Return: list of Element
2730 def get_bookmark_end( 2731 self, 2732 position: int = 0, 2733 name: str | None = None, 2734 ) -> Element | None: 2735 """Return the bookmark end that matches the criteria. 2736 2737 Arguments: 2738 2739 position -- int 2740 2741 name -- str 2742 2743 Return: Element or None if not found 2744 """ 2745 return self._filtered_element( 2746 "descendant::text:bookmark-end", position, text_name=name 2747 )
Return the bookmark end that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2751 def get_reference_marks_single(self) -> list[Element]: 2752 """Return all the reference marks. Search only the tags 2753 text:reference-mark. 2754 Consider using : get_reference_marks() 2755 2756 Return: list of Element 2757 """ 2758 return self._filtered_elements("descendant::text:reference-mark")
Return all the reference marks. Search only the tags text:reference-mark. Consider using : get_reference_marks()
Return: list of Element
2760 def get_reference_mark_single( 2761 self, 2762 position: int = 0, 2763 name: str | None = None, 2764 ) -> Element | None: 2765 """Return the reference mark that matches the criteria. Search only the 2766 tags text:reference-mark. 2767 Consider using : get_reference_mark() 2768 2769 Arguments: 2770 2771 position -- int 2772 2773 name -- str 2774 2775 Return: Element or None if not found 2776 """ 2777 return self._filtered_element( 2778 "descendant::text:reference-mark", position, text_name=name 2779 )
Return the reference mark that matches the criteria. Search only the tags text:reference-mark. Consider using : get_reference_mark()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2781 def get_reference_mark_starts(self) -> list[Element]: 2782 """Return all the reference mark starts. Search only the tags 2783 text:reference-mark-start. 2784 Consider using : get_reference_marks() 2785 2786 Return: list of Element 2787 """ 2788 return self._filtered_elements("descendant::text:reference-mark-start")
Return all the reference mark starts. Search only the tags text:reference-mark-start. Consider using : get_reference_marks()
Return: list of Element
2790 def get_reference_mark_start( 2791 self, 2792 position: int = 0, 2793 name: str | None = None, 2794 ) -> Element | None: 2795 """Return the reference mark start that matches the criteria. Search 2796 only the tags text:reference-mark-start. 2797 Consider using : get_reference_mark() 2798 2799 Arguments: 2800 2801 position -- int 2802 2803 name -- str 2804 2805 Return: Element or None if not found 2806 """ 2807 return self._filtered_element( 2808 "descendant::text:reference-mark-start", position, text_name=name 2809 )
Return the reference mark start that matches the criteria. Search only the tags text:reference-mark-start. Consider using : get_reference_mark()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2811 def get_reference_mark_ends(self) -> list[Element]: 2812 """Return all the reference mark ends. Search only the tags 2813 text:reference-mark-end. 2814 Consider using : get_reference_marks() 2815 2816 Return: list of Element 2817 """ 2818 return self._filtered_elements("descendant::text:reference-mark-end")
Return all the reference mark ends. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()
Return: list of Element
2820 def get_reference_mark_end( 2821 self, 2822 position: int = 0, 2823 name: str | None = None, 2824 ) -> Element | None: 2825 """Return the reference mark end that matches the criteria. Search only 2826 the tags text:reference-mark-end. 2827 Consider using : get_reference_marks() 2828 2829 Arguments: 2830 2831 position -- int 2832 2833 name -- str 2834 2835 Return: Element or None if not found 2836 """ 2837 return self._filtered_element( 2838 "descendant::text:reference-mark-end", position, text_name=name 2839 )
Return the reference mark end that matches the criteria. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2841 def get_reference_marks(self) -> list[Element]: 2842 """Return all the reference marks, either single position reference 2843 (text:reference-mark) or start of range reference 2844 (text:reference-mark-start). 2845 2846 Return: list of Element 2847 """ 2848 return self._filtered_elements( 2849 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2850 )
Return all the reference marks, either single position reference (text:reference-mark) or start of range reference (text:reference-mark-start).
Return: list of Element
2852 def get_reference_mark( 2853 self, 2854 position: int = 0, 2855 name: str | None = None, 2856 ) -> Element | None: 2857 """Return the reference mark that match the criteria. Either single 2858 position reference mark (text:reference-mark) or start of range 2859 reference (text:reference-mark-start). 2860 2861 Arguments: 2862 2863 position -- int 2864 2865 name -- str 2866 2867 Return: Element or None if not found 2868 """ 2869 if name: 2870 request = ( 2871 f"descendant::text:reference-mark-start" 2872 f'[@text:name="{name}"] ' 2873 f"| descendant::text:reference-mark" 2874 f'[@text:name="{name}"]' 2875 ) 2876 return self._filtered_element(request, position=0) 2877 request = ( 2878 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2879 ) 2880 return self._filtered_element(request, position)
Return the reference mark that match the criteria. Either single position reference mark (text:reference-mark) or start of range reference (text:reference-mark-start).
Arguments:
position -- int
name -- str
Return: Element or None if not found
2882 def get_references(self, name: str | None = None) -> list[Element]: 2883 """Return all the references (text:reference-ref). If name is 2884 provided, returns the references of that name. 2885 2886 Return: list of Element 2887 2888 Arguments: 2889 2890 name -- str or None 2891 """ 2892 if name is None: 2893 return self._filtered_elements("descendant::text:reference-ref") 2894 request = f'descendant::text:reference-ref[@text:ref-name="{name}"]' 2895 return self._filtered_elements(request)
Return all the references (text:reference-ref). If name is provided, returns the references of that name.
Return: list of Element
Arguments:
name -- str or None
2901 def get_draw_groups( 2902 self, 2903 title: str | None = None, 2904 description: str | None = None, 2905 content: str | None = None, 2906 ) -> list[Element]: 2907 return self._filtered_elements( 2908 "descendant::draw:g", 2909 svg_title=title, 2910 svg_desc=description, 2911 content=content, 2912 )
2914 def get_draw_group( 2915 self, 2916 position: int = 0, 2917 name: str | None = None, 2918 title: str | None = None, 2919 description: str | None = None, 2920 content: str | None = None, 2921 ) -> Element | None: 2922 return self._filtered_element( 2923 "descendant::draw:g", 2924 position, 2925 draw_name=name, 2926 svg_title=title, 2927 svg_desc=description, 2928 content=content, 2929 )
2933 def get_draw_lines( 2934 self, 2935 draw_style: str | None = None, 2936 draw_text_style: str | None = None, 2937 content: str | None = None, 2938 ) -> list[Element]: 2939 """Return all the draw lines that match the criteria. 2940 2941 Arguments: 2942 2943 draw_style -- str 2944 2945 draw_text_style -- str 2946 2947 content -- str regex 2948 2949 Return: list of odf_shape 2950 """ 2951 return self._filtered_elements( 2952 "descendant::draw:line", 2953 draw_style=draw_style, 2954 draw_text_style=draw_text_style, 2955 content=content, 2956 )
Return all the draw lines that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
2958 def get_draw_line( 2959 self, 2960 position: int = 0, 2961 id: str | None = None, # noqa:A002 2962 content: str | None = None, 2963 ) -> Element | None: 2964 """Return the draw line that matches the criteria. 2965 2966 Arguments: 2967 2968 position -- int 2969 2970 id -- str 2971 2972 content -- str regex 2973 2974 Return: odf_shape or None if not found 2975 """ 2976 return self._filtered_element( 2977 "descendant::draw:line", position, draw_id=id, content=content 2978 )
Return the draw line that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
2982 def get_draw_rectangles( 2983 self, 2984 draw_style: str | None = None, 2985 draw_text_style: str | None = None, 2986 content: str | None = None, 2987 ) -> list[Element]: 2988 """Return all the draw rectangles that match the criteria. 2989 2990 Arguments: 2991 2992 draw_style -- str 2993 2994 draw_text_style -- str 2995 2996 content -- str regex 2997 2998 Return: list of odf_shape 2999 """ 3000 return self._filtered_elements( 3001 "descendant::draw:rect", 3002 draw_style=draw_style, 3003 draw_text_style=draw_text_style, 3004 content=content, 3005 )
Return all the draw rectangles that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3007 def get_draw_rectangle( 3008 self, 3009 position: int = 0, 3010 id: str | None = None, # noqa:A002 3011 content: str | None = None, 3012 ) -> Element | None: 3013 """Return the draw rectangle that matches the criteria. 3014 3015 Arguments: 3016 3017 position -- int 3018 3019 id -- str 3020 3021 content -- str regex 3022 3023 Return: odf_shape or None if not found 3024 """ 3025 return self._filtered_element( 3026 "descendant::draw:rect", position, draw_id=id, content=content 3027 )
Return the draw rectangle that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3031 def get_draw_ellipses( 3032 self, 3033 draw_style: str | None = None, 3034 draw_text_style: str | None = None, 3035 content: str | None = None, 3036 ) -> list[Element]: 3037 """Return all the draw ellipses that match the criteria. 3038 3039 Arguments: 3040 3041 draw_style -- str 3042 3043 draw_text_style -- str 3044 3045 content -- str regex 3046 3047 Return: list of odf_shape 3048 """ 3049 return self._filtered_elements( 3050 "descendant::draw:ellipse", 3051 draw_style=draw_style, 3052 draw_text_style=draw_text_style, 3053 content=content, 3054 )
Return all the draw ellipses that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3056 def get_draw_ellipse( 3057 self, 3058 position: int = 0, 3059 id: str | None = None, # noqa:A002 3060 content: str | None = None, 3061 ) -> Element | None: 3062 """Return the draw ellipse that matches the criteria. 3063 3064 Arguments: 3065 3066 position -- int 3067 3068 id -- str 3069 3070 content -- str regex 3071 3072 Return: odf_shape or None if not found 3073 """ 3074 return self._filtered_element( 3075 "descendant::draw:ellipse", position, draw_id=id, content=content 3076 )
Return the draw ellipse that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3080 def get_draw_connectors( 3081 self, 3082 draw_style: str | None = None, 3083 draw_text_style: str | None = None, 3084 content: str | None = None, 3085 ) -> list[Element]: 3086 """Return all the draw connectors that match the criteria. 3087 3088 Arguments: 3089 3090 draw_style -- str 3091 3092 draw_text_style -- str 3093 3094 content -- str regex 3095 3096 Return: list of odf_shape 3097 """ 3098 return self._filtered_elements( 3099 "descendant::draw:connector", 3100 draw_style=draw_style, 3101 draw_text_style=draw_text_style, 3102 content=content, 3103 )
Return all the draw connectors that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3105 def get_draw_connector( 3106 self, 3107 position: int = 0, 3108 id: str | None = None, # noqa:A002 3109 content: str | None = None, 3110 ) -> Element | None: 3111 """Return the draw connector that matches the criteria. 3112 3113 Arguments: 3114 3115 position -- int 3116 3117 id -- str 3118 3119 content -- str regex 3120 3121 Return: odf_shape or None if not found 3122 """ 3123 return self._filtered_element( 3124 "descendant::draw:connector", position, draw_id=id, content=content 3125 )
Return the draw connector that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3127 def get_orphan_draw_connectors(self) -> list[Element]: 3128 """Return a list of connectors which don't have any shape connected 3129 to them. 3130 """ 3131 connectors = [] 3132 for connector in self.get_draw_connectors(): 3133 start_shape = connector.get_attribute("draw:start-shape") 3134 end_shape = connector.get_attribute("draw:end-shape") 3135 if start_shape is None and end_shape is None: 3136 connectors.append(connector) 3137 return connectors
Return a list of connectors which don't have any shape connected to them.
3141 def get_tracked_changes(self) -> Element | None: 3142 """Return the tracked-changes part in the text body.""" 3143 return self.get_element("//text:tracked-changes")
Return the tracked-changes part in the text body.
3145 def get_changes_ids(self) -> list[Element | Text]: 3146 """Return a list of ids that refers to a change region in the tracked 3147 changes list. 3148 """ 3149 # Insertion changes 3150 xpath_query = "descendant::text:change-start/@text:change-id" 3151 # Deletion changes 3152 xpath_query += " | descendant::text:change/@text:change-id" 3153 return self.xpath(xpath_query)
Return a list of ids that refers to a change region in the tracked changes list.
3155 def get_text_change_deletions(self) -> list[Element]: 3156 """Return all the text changes of deletion kind: the tags text:change. 3157 Consider using : get_text_changes() 3158 3159 Return: list of Element 3160 """ 3161 return self._filtered_elements("descendant::text:text:change")
Return all the text changes of deletion kind: the tags text:change. Consider using : get_text_changes()
Return: list of Element
3163 def get_text_change_deletion( 3164 self, 3165 position: int = 0, 3166 idx: str | None = None, 3167 ) -> Element | None: 3168 """Return the text change of deletion kind that matches the criteria. 3169 Search only for the tags text:change. 3170 Consider using : get_text_change() 3171 3172 Arguments: 3173 3174 position -- int 3175 3176 idx -- str 3177 3178 Return: Element or None if not found 3179 """ 3180 return self._filtered_element( 3181 "descendant::text:change", position, change_id=idx 3182 )
Return the text change of deletion kind that matches the criteria. Search only for the tags text:change. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3184 def get_text_change_starts(self) -> list[Element]: 3185 """Return all the text change-start. Search only for the tags 3186 text:change-start. 3187 Consider using : get_text_changes() 3188 3189 Return: list of Element 3190 """ 3191 return self._filtered_elements("descendant::text:change-start")
Return all the text change-start. Search only for the tags text:change-start. Consider using : get_text_changes()
Return: list of Element
3193 def get_text_change_start( 3194 self, 3195 position: int = 0, 3196 idx: str | None = None, 3197 ) -> Element | None: 3198 """Return the text change-start that matches the criteria. Search 3199 only the tags text:change-start. 3200 Consider using : get_text_change() 3201 3202 Arguments: 3203 3204 position -- int 3205 3206 idx -- str 3207 3208 Return: Element or None if not found 3209 """ 3210 return self._filtered_element( 3211 "descendant::text:change-start", position, change_id=idx 3212 )
Return the text change-start that matches the criteria. Search only the tags text:change-start. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3214 def get_text_change_ends(self) -> list[Element]: 3215 """Return all the text change-end. Search only the tags 3216 text:change-end. 3217 Consider using : get_text_changes() 3218 3219 Return: list of Element 3220 """ 3221 return self._filtered_elements("descendant::text:change-end")
Return all the text change-end. Search only the tags text:change-end. Consider using : get_text_changes()
Return: list of Element
3223 def get_text_change_end( 3224 self, 3225 position: int = 0, 3226 idx: str | None = None, 3227 ) -> Element | None: 3228 """Return the text change-end that matches the criteria. Search only 3229 the tags text:change-end. 3230 Consider using : get_text_change() 3231 3232 Arguments: 3233 3234 position -- int 3235 3236 idx -- str 3237 3238 Return: Element or None if not found 3239 """ 3240 return self._filtered_element( 3241 "descendant::text:change-end", position, change_id=idx 3242 )
Return the text change-end that matches the criteria. Search only the tags text:change-end. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3244 def get_text_changes(self) -> list[Element]: 3245 """Return all the text changes, either single deletion 3246 (text:change) or start of range of changes (text:change-start). 3247 3248 Return: list of Element 3249 """ 3250 request = "descendant::text:change-start | descendant::text:change" 3251 return self._filtered_elements(request)
Return all the text changes, either single deletion (text:change) or start of range of changes (text:change-start).
Return: list of Element
3253 def get_text_change( 3254 self, 3255 position: int = 0, 3256 idx: str | None = None, 3257 ) -> Element | None: 3258 """Return the text change that matches the criteria. Either single 3259 deletion (text:change) or start of range of changes (text:change-start). 3260 position : index of the element to retrieve if several matches, default 3261 is 0. 3262 idx : change-id of the element. 3263 3264 Arguments: 3265 3266 position -- int 3267 3268 idx -- str 3269 3270 Return: Element or None if not found 3271 """ 3272 if idx: 3273 request = ( 3274 f'descendant::text:change-start[@text:change-id="{idx}"] ' 3275 f'| descendant::text:change[@text:change-id="{idx}"]' 3276 ) 3277 return self._filtered_element(request, 0) 3278 request = "descendant::text:change-start | descendant::text:change" 3279 return self._filtered_element(request, position)
Return the text change that matches the criteria. Either single deletion (text:change) or start of range of changes (text:change-start). position : index of the element to retrieve if several matches, default is 0. idx : change-id of the element.
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3283 def get_tocs(self) -> list[Element]: 3284 """Return all the tables of contents. 3285 3286 Return: list of odf_toc 3287 """ 3288 return self._filtered_elements("text:table-of-content")
Return all the tables of contents.
Return: list of odf_toc
3290 def get_toc( 3291 self, 3292 position: int = 0, 3293 content: str | None = None, 3294 ) -> Element | None: 3295 """Return the table of contents that matches the criteria. 3296 3297 Arguments: 3298 3299 position -- int 3300 3301 content -- str regex 3302 3303 Return: odf_toc or None if not found 3304 """ 3305 return self._filtered_element( 3306 "text:table-of-content", position, content=content 3307 )
Return the table of contents that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: odf_toc or None if not found
3334 def get_style( 3335 self, 3336 family: str, 3337 name_or_element: str | Element | None = None, 3338 display_name: str | None = None, 3339 ) -> Element | None: 3340 """Return the style uniquely identified by the family/name pair. If 3341 the argument is already a style object, it will return it. 3342 3343 If the name is not the internal name but the name you gave in the 3344 desktop application, use display_name instead. 3345 3346 Arguments: 3347 3348 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 3349 'number' 3350 3351 name_or_element -- str or Style 3352 3353 display_name -- str 3354 3355 Return: odf_style or None if not found 3356 """ 3357 if isinstance(name_or_element, Element): 3358 name = self.get_attribute("style:name") 3359 if name is not None: 3360 return name_or_element 3361 else: 3362 raise ValueError(f"Not a odf_style ? {name_or_element!r}") 3363 style_name = name_or_element 3364 is_default = not (style_name or display_name) 3365 tagname = self._get_style_tagname(family, is_default=is_default) 3366 # famattr became None if no "style:family" attribute 3367 if family: 3368 return self._filtered_element( 3369 tagname, 3370 0, 3371 style_name=style_name, 3372 display_name=display_name, 3373 family=family, 3374 ) 3375 else: 3376 return self._filtered_element( 3377 tagname, 3378 0, 3379 draw_name=style_name or display_name, 3380 family=family, 3381 )
Return the style uniquely identified by the family/name pair. If the argument is already a style object, it will return it.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number'
name_or_element -- str or Style
display_name -- str
Return: odf_style or None if not found
35class ElementTyped(Element): 36 def set_value_and_type( # noqa: C901 37 self, 38 value: Any, 39 value_type: str | None = None, 40 text: str | None = None, 41 currency: str | None = None, 42 ) -> str | None: 43 # Remove possible previous value and type 44 for name in ( 45 "office:value-type", 46 "office:boolean-value", 47 "office:value", 48 "office:date-value", 49 "office:string-value", 50 "office:time-value", 51 "table:formula", 52 "office:currency", 53 "calcext:value-type", 54 "loext:value-type", 55 ): 56 with contextlib.suppress(KeyError): 57 self.del_attribute(name) 58 if isinstance(value, bytes): 59 value = bytes_to_str(value) 60 if isinstance(value_type, bytes): 61 value_type = bytes_to_str(value_type) 62 if isinstance(text, bytes): 63 text = bytes_to_str(text) 64 if isinstance(currency, bytes): 65 currency = bytes_to_str(currency) 66 if value is None: 67 self._erase_text_content() 68 return text 69 if isinstance(value, bool): 70 if value_type is None: 71 value_type = "boolean" 72 if text is None: 73 text = "true" if value else "false" 74 value = Boolean.encode(value) 75 elif isinstance(value, (int, float, Decimal)): 76 if value_type == "percentage": 77 text = "%d %%" % int(value * 100) 78 if value_type is None: 79 value_type = "float" 80 if text is None: 81 text = str(value) 82 value = str(value) 83 elif isinstance(value, datetime): 84 if value_type is None: 85 value_type = "date" 86 if text is None: 87 text = str(DateTime.encode(value)) 88 value = DateTime.encode(value) 89 elif isinstance(value, date): 90 if value_type is None: 91 value_type = "date" 92 if text is None: 93 text = str(Date.encode(value)) 94 value = Date.encode(value) 95 elif isinstance(value, str): 96 if value_type is None: 97 value_type = "string" 98 if text is None: 99 text = value 100 elif isinstance(value, timedelta): 101 if value_type is None: 102 value_type = "time" 103 if text is None: 104 text = str(Duration.encode(value)) 105 value = Duration.encode(value) 106 elif value is not None: 107 raise TypeError(f"Type unknown: '{value!r}'") 108 109 if value_type is not None: 110 self.set_attribute("office:value-type", value_type) 111 self.set_attribute("calcext:value-type", value_type) 112 if value_type == "boolean": 113 self.set_attribute("office:boolean-value", value) 114 elif value_type == "currency": 115 self.set_attribute("office:value", value) 116 self.set_attribute("office:currency", currency) 117 elif value_type == "date": 118 self.set_attribute("office:date-value", value) 119 elif value_type in ("float", "percentage"): 120 self.set_attribute("office:value", value) 121 self.set_attribute("calcext:value", value) 122 elif value_type == "string": 123 self.set_attribute("office:string-value", value) 124 elif value_type == "time": 125 self.set_attribute("office:time-value", value) 126 127 return text 128 129 def _get_typed_value( # noqa: C901 130 self, 131 value_type: str | None = None, 132 try_get_text: bool = True, 133 ) -> tuple[Any, str | None]: 134 """Return Python typed value. 135 136 Only for "with office:value-type" elements, not for meta fields.""" 137 value: Decimal | str | bool | None = None 138 if value_type is None: 139 read_value_type = self.get_attribute("office:value-type") 140 if isinstance(read_value_type, bool): 141 raise TypeError( 142 f'Wrong type for "office:value-type": {type(read_value_type)}' 143 ) 144 value_type = read_value_type 145 # value_type = to_str(value_type) 146 if value_type == "boolean": 147 value = self.get_attribute("office:boolean-value") 148 return (value, value_type) 149 if value_type in {"float", "percentage", "currency"}: 150 read_number = self.get_attribute("office:value") 151 if not isinstance(read_number, (Decimal, str)): 152 raise TypeError(f'Wrong type for "office:value": {type(read_number)}') 153 value = Decimal(read_number) 154 # Return 3 instead of 3.0 if possible 155 if int(value) == value: 156 return (int(value), value_type) 157 return (value, value_type) 158 if value_type == "date": 159 read_attribute = self.get_attribute("office:date-value") 160 if not isinstance(read_attribute, str): 161 raise TypeError( 162 f'Wrong type for "office:date-value": {type(read_attribute)}' 163 ) 164 if "T" in read_attribute: 165 return (DateTime.decode(read_attribute), value_type) 166 return (Date.decode(read_attribute), value_type) 167 if value_type == "string": 168 value = self.get_attribute("office:string-value") 169 if value is not None: 170 return (str(value), value_type) 171 if try_get_text: 172 list_value = [ 173 para.text_recursive for para in self.get_elements("text:p") 174 ] 175 if list_value: 176 return ("\n".join(list_value), value_type) 177 return (None, value_type) 178 if value_type == "time": 179 read_value = self.get_attribute("office:time-value") 180 if not isinstance(read_value, str): 181 raise TypeError( 182 f'Wrong type for "office:time-value": {type(read_value)}' 183 ) 184 time_value = Duration.decode(read_value) 185 return (time_value, value_type) 186 if value_type is None: 187 return (None, None) 188 raise ValueError(f'Unexpected value type: "{value_type}"') 189 190 def get_value( 191 self, 192 value_type: str | None = None, 193 try_get_text: bool = True, 194 get_type: bool = False, 195 ) -> Any | tuple[Any, str]: 196 """Return Python typed value. 197 198 Only for "with office:value-type" elements, not for meta fields.""" 199 if get_type: 200 return self._get_typed_value( 201 value_type=value_type, 202 try_get_text=try_get_text, 203 ) 204 return self._get_typed_value( 205 value_type=value_type, 206 try_get_text=try_get_text, 207 )[0]
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
36 def set_value_and_type( # noqa: C901 37 self, 38 value: Any, 39 value_type: str | None = None, 40 text: str | None = None, 41 currency: str | None = None, 42 ) -> str | None: 43 # Remove possible previous value and type 44 for name in ( 45 "office:value-type", 46 "office:boolean-value", 47 "office:value", 48 "office:date-value", 49 "office:string-value", 50 "office:time-value", 51 "table:formula", 52 "office:currency", 53 "calcext:value-type", 54 "loext:value-type", 55 ): 56 with contextlib.suppress(KeyError): 57 self.del_attribute(name) 58 if isinstance(value, bytes): 59 value = bytes_to_str(value) 60 if isinstance(value_type, bytes): 61 value_type = bytes_to_str(value_type) 62 if isinstance(text, bytes): 63 text = bytes_to_str(text) 64 if isinstance(currency, bytes): 65 currency = bytes_to_str(currency) 66 if value is None: 67 self._erase_text_content() 68 return text 69 if isinstance(value, bool): 70 if value_type is None: 71 value_type = "boolean" 72 if text is None: 73 text = "true" if value else "false" 74 value = Boolean.encode(value) 75 elif isinstance(value, (int, float, Decimal)): 76 if value_type == "percentage": 77 text = "%d %%" % int(value * 100) 78 if value_type is None: 79 value_type = "float" 80 if text is None: 81 text = str(value) 82 value = str(value) 83 elif isinstance(value, datetime): 84 if value_type is None: 85 value_type = "date" 86 if text is None: 87 text = str(DateTime.encode(value)) 88 value = DateTime.encode(value) 89 elif isinstance(value, date): 90 if value_type is None: 91 value_type = "date" 92 if text is None: 93 text = str(Date.encode(value)) 94 value = Date.encode(value) 95 elif isinstance(value, str): 96 if value_type is None: 97 value_type = "string" 98 if text is None: 99 text = value 100 elif isinstance(value, timedelta): 101 if value_type is None: 102 value_type = "time" 103 if text is None: 104 text = str(Duration.encode(value)) 105 value = Duration.encode(value) 106 elif value is not None: 107 raise TypeError(f"Type unknown: '{value!r}'") 108 109 if value_type is not None: 110 self.set_attribute("office:value-type", value_type) 111 self.set_attribute("calcext:value-type", value_type) 112 if value_type == "boolean": 113 self.set_attribute("office:boolean-value", value) 114 elif value_type == "currency": 115 self.set_attribute("office:value", value) 116 self.set_attribute("office:currency", currency) 117 elif value_type == "date": 118 self.set_attribute("office:date-value", value) 119 elif value_type in ("float", "percentage"): 120 self.set_attribute("office:value", value) 121 self.set_attribute("calcext:value", value) 122 elif value_type == "string": 123 self.set_attribute("office:string-value", value) 124 elif value_type == "time": 125 self.set_attribute("office:time-value", value) 126 127 return text
190 def get_value( 191 self, 192 value_type: str | None = None, 193 try_get_text: bool = True, 194 get_type: bool = False, 195 ) -> Any | tuple[Any, str]: 196 """Return Python typed value. 197 198 Only for "with office:value-type" elements, not for meta fields.""" 199 if get_type: 200 return self._get_typed_value( 201 value_type=value_type, 202 try_get_text=try_get_text, 203 ) 204 return self._get_typed_value( 205 value_type=value_type, 206 try_get_text=try_get_text, 207 )[0]
Return Python typed value.
Only for "with office:value-type" elements, not for meta fields.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
194class EllipseShape(ShapeBase): 195 """Create a ellipse shape. 196 197 Arguments: 198 199 style -- str 200 201 text_style -- str 202 203 draw_id -- str 204 205 layer -- str 206 207 position -- (str, str) 208 209 size -- (str, str) 210 211 """ 212 213 _tag = "draw:ellipse" 214 _properties: tuple[PropDef, ...] = () 215 216 def __init__( 217 self, 218 style: str | None = None, 219 text_style: str | None = None, 220 draw_id: str | None = None, 221 layer: str | None = None, 222 position: tuple | None = None, 223 size: tuple | None = None, 224 **kwargs: Any, 225 ) -> None: 226 kwargs.update( 227 { 228 "style": style, 229 "text_style": text_style, 230 "draw_id": draw_id, 231 "layer": layer, 232 "size": size, 233 "position": position, 234 } 235 ) 236 super().__init__(**kwargs)
Create a ellipse shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
position -- (str, str)
size -- (str, str)
216 def __init__( 217 self, 218 style: str | None = None, 219 text_style: str | None = None, 220 draw_id: str | None = None, 221 layer: str | None = None, 222 position: tuple | None = None, 223 size: tuple | None = None, 224 **kwargs: Any, 225 ) -> None: 226 kwargs.update( 227 { 228 "style": style, 229 "text_style": text_style, 230 "draw_id": draw_id, 231 "layer": layer, 232 "size": size, 233 "position": position, 234 } 235 ) 236 super().__init__(**kwargs)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
168class Frame(Element, AnchorMix, PosMix, ZMix, SizeMix): 169 """ODF Frame "draw:frame" 170 171 Frames are not useful by themselves. You should consider calling 172 Frame.image_frame() or Frame.text_frame directly. 173 """ 174 175 _tag = "draw:frame" 176 _properties = ( 177 PropDef("name", "draw:name"), 178 PropDef("draw_id", "draw:id"), 179 PropDef("width", "svg:width"), 180 PropDef("height", "svg:height"), 181 PropDef("style", "draw:style-name"), 182 PropDef("pos_x", "svg:x"), 183 PropDef("pos_y", "svg:y"), 184 PropDef("presentation_class", "presentation:class"), 185 PropDef("layer", "draw:layer"), 186 PropDef("presentation_style", "presentation:style-name"), 187 ) 188 189 def __init__( # noqa: C901 190 self, 191 name: str | None = None, 192 draw_id: str | None = None, 193 style: str | None = None, 194 position: tuple | None = None, 195 size: tuple = ("1cm", "1cm"), 196 z_index: int = 0, 197 presentation_class: str | None = None, 198 anchor_type: str | None = None, 199 anchor_page: int | None = None, 200 layer: str | None = None, 201 presentation_style: str | None = None, 202 **kwargs: Any, 203 ) -> None: 204 """Create a frame element of the given size. Position is relative to the 205 context the frame is inserted in. If positioned by page, give the page 206 number and the x, y position. 207 208 Size is a (width, height) tuple and position is a (left, top) tuple; items 209 are strings including the unit, e.g. ('10cm', '15cm'). 210 211 Frames are not useful by themselves. You should consider calling: 212 Frame.image_frame() 213 or 214 Frame.text_frame() 215 216 217 Arguments: 218 219 name -- str 220 221 draw_id -- str 222 223 style -- str 224 225 position -- (str, str) 226 227 size -- (str, str) 228 229 z_index -- int (default 0) 230 231 presentation_class -- str 232 233 anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char' 234 235 anchor_page -- int, page number is anchor_type is 'page' 236 237 layer -- str 238 239 presentation_style -- str 240 """ 241 super().__init__(**kwargs) 242 if self._do_init: 243 self.size = size 244 self.z_index = z_index 245 if name: 246 self.name = name 247 if draw_id is not None: 248 self.draw_id = draw_id 249 if style is not None: 250 self.style = style 251 if position is not None: 252 self.position = position 253 if presentation_class is not None: 254 self.presentation_class = presentation_class 255 if anchor_type: 256 self.anchor_type = anchor_type 257 if position and not anchor_type: 258 self.anchor_type = "paragraph" 259 if anchor_page is not None: 260 self.anchor_page = anchor_page 261 if layer is not None: 262 self.layer = layer 263 if presentation_style is not None: 264 self.presentation_style = presentation_style 265 266 @classmethod 267 def image_frame( 268 cls, 269 image: Element | str, 270 text: str | None = None, 271 name: str | None = None, 272 draw_id: str | None = None, 273 style: str | None = None, 274 position: tuple | None = None, 275 size: tuple = ("1cm", "1cm"), 276 z_index: int = 0, 277 presentation_class: str | None = None, 278 anchor_type: str | None = None, 279 anchor_page: int | None = None, 280 layer: str | None = None, 281 presentation_style: str | None = None, 282 **kwargs: Any, 283 ) -> Element: 284 """Create a ready-to-use image, since image must be embedded in a 285 frame. 286 287 The optionnal text will appear above the image. 288 289 Arguments: 290 291 image -- DrawImage or str, DrawImage element or URL of the image 292 293 text -- str, text for the image 294 295 See Frame() initialization for the other arguments 296 297 Return: Frame 298 """ 299 frame = cls( 300 name=name, 301 draw_id=draw_id, 302 style=style, 303 position=position, 304 size=size, 305 z_index=z_index, 306 presentation_class=presentation_class, 307 anchor_type=anchor_type, 308 anchor_page=anchor_page, 309 layer=layer, 310 presentation_style=presentation_style, 311 **kwargs, 312 ) 313 image_element = frame.set_image(image) 314 if text: 315 image_element.text_content = text 316 return frame 317 318 @classmethod 319 def text_frame( 320 cls, 321 text_or_element: Iterable[Element] | Element | str, 322 text_style: str | None = None, 323 name: str | None = None, 324 draw_id: str | None = None, 325 style: str | None = None, 326 position: tuple | None = None, 327 size: tuple = ("1cm", "1cm"), 328 z_index: int = 0, 329 presentation_class: str | None = None, 330 anchor_type: str | None = None, 331 anchor_page: int | None = None, 332 layer: str | None = None, 333 presentation_style: str | None = None, 334 **kwargs: Any, 335 ) -> Element: 336 """Create a ready-to-use text box, since text box must be embedded in 337 a frame. 338 339 The optionnal text will appear above the image. 340 341 Arguments: 342 343 text_or_element -- str or Element, or list of them, text content 344 of the text box. 345 346 text_style -- str, name of the style for the text 347 348 See Frame() initialization for the other arguments 349 350 Return: Frame 351 """ 352 frame = cls( 353 name=name, 354 draw_id=draw_id, 355 style=style, 356 position=position, 357 size=size, 358 z_index=z_index, 359 presentation_class=presentation_class, 360 anchor_type=anchor_type, 361 anchor_page=anchor_page, 362 layer=layer, 363 presentation_style=presentation_style, 364 **kwargs, 365 ) 366 frame.set_text_box(text_or_element, text_style) 367 return frame 368 369 @property 370 def text_content(self) -> str: 371 text_box = self.get_element("draw:text-box") 372 if text_box is None: 373 return "" 374 return text_box.text_content 375 376 @text_content.setter 377 def text_content(self, text_or_element: Element | str) -> None: 378 text_box = self.get_element("draw:text-box") 379 if text_box is None: 380 text_box = Element.from_tag("draw:text-box") 381 self.append(text_box) 382 if isinstance(text_or_element, Element): 383 text_box.clear() 384 text_box.append(text_or_element) 385 else: 386 text_box.text_content = text_or_element 387 388 def get_image( 389 self, 390 position: int = 0, 391 name: str | None = None, 392 url: str | None = None, 393 content: str | None = None, 394 ) -> Element | None: 395 return self.get_element("draw:image") 396 397 def set_image(self, url_or_element: Element | str) -> Element: 398 image = self.get_image() 399 if image is None: 400 if isinstance(url_or_element, Element): 401 image = url_or_element 402 self.append(image) 403 else: 404 image = DrawImage(url_or_element) 405 self.append(image) 406 else: 407 if isinstance(url_or_element, Element): 408 image.delete() 409 image = url_or_element 410 self.append(image) 411 else: 412 image.set_url(url_or_element) # type: ignore 413 return image 414 415 def get_text_box(self) -> Element | None: 416 return self.get_element("draw:text-box") 417 418 def set_text_box( 419 self, 420 text_or_element: Iterable[Element | str] | Element | str, 421 text_style: str | None = None, 422 ) -> Element: 423 text_box = self.get_text_box() 424 if text_box is None: 425 text_box = Element.from_tag("draw:text-box") 426 self.append(text_box) 427 else: 428 text_box.clear() 429 if isinstance(text_or_element, (Element, str)): 430 text_or_element_list: Iterable[Element | str] = [text_or_element] 431 else: 432 text_or_element_list = text_or_element 433 for item in text_or_element_list: 434 if isinstance(item, str): 435 text_box.append(Paragraph(item, style=text_style)) 436 else: 437 text_box.append(item) 438 return text_box 439 440 @staticmethod 441 def _get_formatted_text_subresult(context: dict, element: Element) -> str: 442 str_list = [" "] 443 for child in element.children: 444 str_list.append(child.get_formatted_text(context)) 445 subresult = "".join(str_list) 446 subresult = subresult.replace("\n", "\n ") 447 return subresult.rstrip(" ") 448 449 def get_formatted_text( # noqa: C901 450 self, 451 context: dict | None = None, 452 ) -> str: 453 if not context: 454 context = {} 455 result = [] 456 for element in self.children: 457 tag = element.tag 458 if tag == "draw:image": 459 if context["rst_mode"]: 460 filename = element.get_attribute("xlink:href") 461 462 # Compute width and height 463 width, height = self.size 464 if width is not None: 465 width = Unit(width) 466 width = width.convert("px", DPI) 467 if height is not None: 468 height = Unit(height) 469 height = height.convert("px", DPI) 470 471 # Insert or not ? 472 if context["no_img_level"]: 473 context["img_counter"] += 1 474 ref = f"|img{context['img_counter']}|" 475 result.append(ref) 476 context["images"].append((ref, filename, (width, height))) 477 else: 478 result.append(f"\n.. image:: {filename}\n") 479 if width is not None: 480 result.append(f" :width: {width}\n") 481 if height is not None: 482 result.append(f" :height: {height}\n") 483 else: 484 result.append(f"[Image {element.get_attribute('xlink:href')}]\n") 485 elif tag == "draw:text-box": 486 result.append(self._get_formatted_text_subresult(context, element)) 487 else: 488 result.append(element.get_formatted_text(context)) 489 result.append("\n") 490 return "".join(result)
ODF Frame "draw:frame"
Frames are not useful by themselves. You should consider calling Frame.image_frame() or Frame.text_frame directly.
189 def __init__( # noqa: C901 190 self, 191 name: str | None = None, 192 draw_id: str | None = None, 193 style: str | None = None, 194 position: tuple | None = None, 195 size: tuple = ("1cm", "1cm"), 196 z_index: int = 0, 197 presentation_class: str | None = None, 198 anchor_type: str | None = None, 199 anchor_page: int | None = None, 200 layer: str | None = None, 201 presentation_style: str | None = None, 202 **kwargs: Any, 203 ) -> None: 204 """Create a frame element of the given size. Position is relative to the 205 context the frame is inserted in. If positioned by page, give the page 206 number and the x, y position. 207 208 Size is a (width, height) tuple and position is a (left, top) tuple; items 209 are strings including the unit, e.g. ('10cm', '15cm'). 210 211 Frames are not useful by themselves. You should consider calling: 212 Frame.image_frame() 213 or 214 Frame.text_frame() 215 216 217 Arguments: 218 219 name -- str 220 221 draw_id -- str 222 223 style -- str 224 225 position -- (str, str) 226 227 size -- (str, str) 228 229 z_index -- int (default 0) 230 231 presentation_class -- str 232 233 anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char' 234 235 anchor_page -- int, page number is anchor_type is 'page' 236 237 layer -- str 238 239 presentation_style -- str 240 """ 241 super().__init__(**kwargs) 242 if self._do_init: 243 self.size = size 244 self.z_index = z_index 245 if name: 246 self.name = name 247 if draw_id is not None: 248 self.draw_id = draw_id 249 if style is not None: 250 self.style = style 251 if position is not None: 252 self.position = position 253 if presentation_class is not None: 254 self.presentation_class = presentation_class 255 if anchor_type: 256 self.anchor_type = anchor_type 257 if position and not anchor_type: 258 self.anchor_type = "paragraph" 259 if anchor_page is not None: 260 self.anchor_page = anchor_page 261 if layer is not None: 262 self.layer = layer 263 if presentation_style is not None: 264 self.presentation_style = presentation_style
Create a frame element of the given size. Position is relative to the context the frame is inserted in. If positioned by page, give the page number and the x, y position.
Size is a (width, height) tuple and position is a (left, top) tuple; items are strings including the unit, e.g. ('10cm', '15cm').
Frames are not useful by themselves. You should consider calling: Frame.image_frame() or Frame.text_frame()
Arguments:
name -- str
draw_id -- str
style -- str
position -- (str, str)
size -- (str, str)
z_index -- int (default 0)
presentation_class -- str
anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
anchor_page -- int, page number is anchor_type is 'page'
layer -- str
presentation_style -- str
266 @classmethod 267 def image_frame( 268 cls, 269 image: Element | str, 270 text: str | None = None, 271 name: str | None = None, 272 draw_id: str | None = None, 273 style: str | None = None, 274 position: tuple | None = None, 275 size: tuple = ("1cm", "1cm"), 276 z_index: int = 0, 277 presentation_class: str | None = None, 278 anchor_type: str | None = None, 279 anchor_page: int | None = None, 280 layer: str | None = None, 281 presentation_style: str | None = None, 282 **kwargs: Any, 283 ) -> Element: 284 """Create a ready-to-use image, since image must be embedded in a 285 frame. 286 287 The optionnal text will appear above the image. 288 289 Arguments: 290 291 image -- DrawImage or str, DrawImage element or URL of the image 292 293 text -- str, text for the image 294 295 See Frame() initialization for the other arguments 296 297 Return: Frame 298 """ 299 frame = cls( 300 name=name, 301 draw_id=draw_id, 302 style=style, 303 position=position, 304 size=size, 305 z_index=z_index, 306 presentation_class=presentation_class, 307 anchor_type=anchor_type, 308 anchor_page=anchor_page, 309 layer=layer, 310 presentation_style=presentation_style, 311 **kwargs, 312 ) 313 image_element = frame.set_image(image) 314 if text: 315 image_element.text_content = text 316 return frame
Create a ready-to-use image, since image must be embedded in a frame.
The optionnal text will appear above the image.
Arguments:
image -- DrawImage or str, DrawImage element or URL of the image
text -- str, text for the image
See Frame() initialization for the other arguments
Return: Frame
318 @classmethod 319 def text_frame( 320 cls, 321 text_or_element: Iterable[Element] | Element | str, 322 text_style: str | None = None, 323 name: str | None = None, 324 draw_id: str | None = None, 325 style: str | None = None, 326 position: tuple | None = None, 327 size: tuple = ("1cm", "1cm"), 328 z_index: int = 0, 329 presentation_class: str | None = None, 330 anchor_type: str | None = None, 331 anchor_page: int | None = None, 332 layer: str | None = None, 333 presentation_style: str | None = None, 334 **kwargs: Any, 335 ) -> Element: 336 """Create a ready-to-use text box, since text box must be embedded in 337 a frame. 338 339 The optionnal text will appear above the image. 340 341 Arguments: 342 343 text_or_element -- str or Element, or list of them, text content 344 of the text box. 345 346 text_style -- str, name of the style for the text 347 348 See Frame() initialization for the other arguments 349 350 Return: Frame 351 """ 352 frame = cls( 353 name=name, 354 draw_id=draw_id, 355 style=style, 356 position=position, 357 size=size, 358 z_index=z_index, 359 presentation_class=presentation_class, 360 anchor_type=anchor_type, 361 anchor_page=anchor_page, 362 layer=layer, 363 presentation_style=presentation_style, 364 **kwargs, 365 ) 366 frame.set_text_box(text_or_element, text_style) 367 return frame
Create a ready-to-use text box, since text box must be embedded in a frame.
The optionnal text will appear above the image.
Arguments:
text_or_element -- str or Element, or list of them, text content
of the text box.
text_style -- str, name of the style for the text
See Frame() initialization for the other arguments
Return: Frame
369 @property 370 def text_content(self) -> str: 371 text_box = self.get_element("draw:text-box") 372 if text_box is None: 373 return "" 374 return text_box.text_content
Get / set the text of the embedded paragraph, including embeded annotations, cells...
Set create a paragraph if missing
388 def get_image( 389 self, 390 position: int = 0, 391 name: str | None = None, 392 url: str | None = None, 393 content: str | None = None, 394 ) -> Element | None: 395 return self.get_element("draw:image")
Return the image matching the criteria.
Arguments:
position -- int
name -- str
url -- str regex
content -- str regex
Return: Element or None if not found
397 def set_image(self, url_or_element: Element | str) -> Element: 398 image = self.get_image() 399 if image is None: 400 if isinstance(url_or_element, Element): 401 image = url_or_element 402 self.append(image) 403 else: 404 image = DrawImage(url_or_element) 405 self.append(image) 406 else: 407 if isinstance(url_or_element, Element): 408 image.delete() 409 image = url_or_element 410 self.append(image) 411 else: 412 image.set_url(url_or_element) # type: ignore 413 return image
418 def set_text_box( 419 self, 420 text_or_element: Iterable[Element | str] | Element | str, 421 text_style: str | None = None, 422 ) -> Element: 423 text_box = self.get_text_box() 424 if text_box is None: 425 text_box = Element.from_tag("draw:text-box") 426 self.append(text_box) 427 else: 428 text_box.clear() 429 if isinstance(text_or_element, (Element, str)): 430 text_or_element_list: Iterable[Element | str] = [text_or_element] 431 else: 432 text_or_element_list = text_or_element 433 for item in text_or_element_list: 434 if isinstance(item, str): 435 text_box.append(Paragraph(item, style=text_style)) 436 else: 437 text_box.append(item) 438 return text_box
449 def get_formatted_text( # noqa: C901 450 self, 451 context: dict | None = None, 452 ) -> str: 453 if not context: 454 context = {} 455 result = [] 456 for element in self.children: 457 tag = element.tag 458 if tag == "draw:image": 459 if context["rst_mode"]: 460 filename = element.get_attribute("xlink:href") 461 462 # Compute width and height 463 width, height = self.size 464 if width is not None: 465 width = Unit(width) 466 width = width.convert("px", DPI) 467 if height is not None: 468 height = Unit(height) 469 height = height.convert("px", DPI) 470 471 # Insert or not ? 472 if context["no_img_level"]: 473 context["img_counter"] += 1 474 ref = f"|img{context['img_counter']}|" 475 result.append(ref) 476 context["images"].append((ref, filename, (width, height))) 477 else: 478 result.append(f"\n.. image:: {filename}\n") 479 if width is not None: 480 result.append(f" :width: {width}\n") 481 if height is not None: 482 result.append(f" :height: {height}\n") 483 else: 484 result.append(f"[Image {element.get_attribute('xlink:href')}]\n") 485 elif tag == "draw:text-box": 486 result.append(self._get_formatted_text_subresult(context, element)) 487 else: 488 result.append(element.get_formatted_text(context)) 489 result.append("\n") 490 return "".join(result)
This function should return a beautiful version of the text.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.AnchorMix
- ANCHOR_VALUE_CHOICE
- anchor_type
- anchor_page
- odfdo.frame.PosMix
- position
- odfdo.frame.ZMix
- z_index
- odfdo.frame.SizeMix
- size
33class Header(Paragraph): 34 """Specialised paragraph for headings "text:h".""" 35 36 _tag = "text:h" 37 _properties = ( 38 PropDef("level", "text:outline-level"), 39 PropDef("restart_numbering", "text:restart-numbering"), 40 PropDef("start_value", "text:start-value"), 41 PropDef("suppress_numbering", "text:suppress-numbering"), 42 ) 43 44 def __init__( 45 self, 46 level: int = 1, 47 text: str | None = None, 48 restart_numbering: bool = False, 49 start_value: int | None = None, 50 suppress_numbering: bool = False, 51 style: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 """Create a header element of the given style and level, containing the 55 optional given text. 56 57 Level count begins at 1. 58 59 Arguments: 60 61 level -- int 62 63 text -- str 64 65 restart_numbering -- bool 66 67 start_value -- int 68 69 style -- str 70 """ 71 super().__init__(**kwargs) 72 if self._do_init: 73 self.level = int(level) 74 if text: 75 self.text = text 76 if restart_numbering: 77 self.restart_numbering = True 78 if start_value is not None: 79 self.start_value = start_value 80 if suppress_numbering: 81 self.suppress_numbering = True 82 if style: 83 self.style = style 84 85 def get_formatted_text( 86 self, 87 context: dict | None = None, 88 simple: bool = False, 89 ) -> str: 90 if not context: 91 context = { 92 "document": None, 93 "footnotes": [], 94 "endnotes": [], 95 "annotations": [], 96 "rst_mode": False, 97 "img_counter": 0, 98 "images": [], 99 "no_img_level": 0, 100 } 101 context["no_img_level"] += 1 102 title = super().get_formatted_text(context) 103 context["no_img_level"] -= 1 104 title = title.strip() 105 title = sub(r"\s+", " ", title) 106 107 # No rst_mode ? 108 if not context["rst_mode"]: 109 return title 110 # If here in rst_mode! 111 112 # Get the level, max 5! 113 LEVEL_STYLES = "#=-~`+^°'." 114 level = int(self.level) 115 if level > len(LEVEL_STYLES): 116 raise ValueError("Too many levels of heading") 117 118 # And return the result 119 result = ["\n", title, "\n", LEVEL_STYLES[level - 1] * len(title), "\n"] 120 return "".join(result)
Specialised paragraph for headings "text:h".
44 def __init__( 45 self, 46 level: int = 1, 47 text: str | None = None, 48 restart_numbering: bool = False, 49 start_value: int | None = None, 50 suppress_numbering: bool = False, 51 style: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 """Create a header element of the given style and level, containing the 55 optional given text. 56 57 Level count begins at 1. 58 59 Arguments: 60 61 level -- int 62 63 text -- str 64 65 restart_numbering -- bool 66 67 start_value -- int 68 69 style -- str 70 """ 71 super().__init__(**kwargs) 72 if self._do_init: 73 self.level = int(level) 74 if text: 75 self.text = text 76 if restart_numbering: 77 self.restart_numbering = True 78 if start_value is not None: 79 self.start_value = start_value 80 if suppress_numbering: 81 self.suppress_numbering = True 82 if style: 83 self.style = style
Create a header element of the given style and level, containing the optional given text.
Level count begins at 1.
Arguments:
level -- int
text -- str
restart_numbering -- bool
start_value -- int
style -- str
85 def get_formatted_text( 86 self, 87 context: dict | None = None, 88 simple: bool = False, 89 ) -> str: 90 if not context: 91 context = { 92 "document": None, 93 "footnotes": [], 94 "endnotes": [], 95 "annotations": [], 96 "rst_mode": False, 97 "img_counter": 0, 98 "images": [], 99 "no_img_level": 0, 100 } 101 context["no_img_level"] += 1 102 title = super().get_formatted_text(context) 103 context["no_img_level"] -= 1 104 title = title.strip() 105 title = sub(r"\s+", " ", title) 106 107 # No rst_mode ? 108 if not context["rst_mode"]: 109 return title 110 # If here in rst_mode! 111 112 # Get the level, max 5! 113 LEVEL_STYLES = "#=-~`+^°'." 114 level = int(self.level) 115 if level > len(LEVEL_STYLES): 116 raise ValueError("Too many levels of heading") 117 118 # And return the result 119 result = ["\n", title, "\n", LEVEL_STYLES[level - 1] * len(title), "\n"] 120 return "".join(result)
This function should return a beautiful version of the text.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Paragraph
- insert_note
- insert_annotation
- insert_annotation_end
- set_reference_mark
- set_reference_mark_end
- insert_variable
- set_span
- remove_spans
- remove_span
- set_link
- remove_links
- remove_link
- insert_reference
- set_bookmark
- odfdo.paragraph_base.ParagraphBase
- append_plain_text
- style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
40class IndexTitle(Element): 41 """The "text:index-title" element contains the title of an index. 42 43 The element has the following attributes: 44 text:name, text:protected, text:protection-key, 45 text:protection-key-digest-algorithm, text:style-name, xml:id. 46 47 The actual title is stored in a child element 48 """ 49 50 _tag = "text:index-title" 51 _properties = ( 52 PropDef("name", "text:name"), 53 PropDef("style", "text:style-name"), 54 PropDef("xml_id", "xml:id"), 55 PropDef("protected", "text:protected"), 56 PropDef("protection_key", "text:protection-key"), 57 PropDef( 58 "protection_key_digest_algorithm", "text:protection-key-digest-algorithm" 59 ), 60 ) 61 62 def __init__( 63 self, 64 name: str | None = None, 65 style: str | None = None, 66 title_text: str | None = None, 67 title_text_style: str | None = None, 68 xml_id: str | None = None, 69 **kwargs: Any, 70 ) -> None: 71 super().__init__(**kwargs) 72 if self._do_init: 73 if name: 74 self.name = name 75 if style: 76 self.style = style 77 if xml_id: 78 self.xml_id = xml_id 79 if title_text: 80 self.set_title_text(title_text, title_text_style) 81 82 def set_title_text( 83 self, 84 title_text: str, 85 title_text_style: str | None = None, 86 ) -> None: 87 title = Paragraph(title_text, style=title_text_style) 88 self.append(title)
The "text:index-title" element contains the title of an index.
The element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name, xml:id.
The actual title is stored in a child element
62 def __init__( 63 self, 64 name: str | None = None, 65 style: str | None = None, 66 title_text: str | None = None, 67 title_text_style: str | None = None, 68 xml_id: str | None = None, 69 **kwargs: Any, 70 ) -> None: 71 super().__init__(**kwargs) 72 if self._do_init: 73 if name: 74 self.name = name 75 if style: 76 self.style = style 77 if xml_id: 78 self.xml_id = xml_id 79 if title_text: 80 self.set_title_text(title_text, title_text_style)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
447class IndexTitleTemplate(Element): 448 """ODF "text:index-title-template" 449 450 Arguments: 451 452 style -- str 453 """ 454 455 _tag = "text:index-title-template" 456 _properties = (PropDef("style", "text:style-name"),) 457 458 def __init__(self, style: str | None = None, **kwargs: Any) -> None: 459 super().__init__(**kwargs) 460 if self._do_init and style: 461 self.style = style
ODF "text:index-title-template"
Arguments:
style -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
223class LineBreak(Element): 224 """This element represents a line break "text:line-break" """ 225 226 _tag = "text:line-break" 227 228 def __init__(self, **kwargs: Any) -> None: 229 super().__init__(**kwargs)
This element represents a line break "text:line-break"
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
89class LineShape(ShapeBase): 90 """Create a line shape. 91 92 Arguments: 93 94 style -- str 95 96 text_style -- str 97 98 draw_id -- str 99 100 layer -- str 101 102 p1 -- (str, str) 103 104 p2 -- (str, str) 105 """ 106 107 _tag = "draw:line" 108 _properties: tuple[PropDef, ...] = ( 109 PropDef("x1", "svg:x1"), 110 PropDef("y1", "svg:y1"), 111 PropDef("x2", "svg:x2"), 112 PropDef("y2", "svg:y2"), 113 ) 114 115 def __init__( 116 self, 117 style: str | None = None, 118 text_style: str | None = None, 119 draw_id: str | None = None, 120 layer: str | None = None, 121 p1: tuple | None = None, 122 p2: tuple | None = None, 123 **kwargs: Any, 124 ) -> None: 125 kwargs.update( 126 { 127 "style": style, 128 "text_style": text_style, 129 "draw_id": draw_id, 130 "layer": layer, 131 } 132 ) 133 super().__init__(**kwargs) 134 if self._do_init: 135 if p1: 136 self.x1 = p1[0] 137 self.y1 = p1[1] 138 if p2: 139 self.x2 = p2[0] 140 self.y2 = p2[1]
Create a line shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
p1 -- (str, str)
p2 -- (str, str)
115 def __init__( 116 self, 117 style: str | None = None, 118 text_style: str | None = None, 119 draw_id: str | None = None, 120 layer: str | None = None, 121 p1: tuple | None = None, 122 p2: tuple | None = None, 123 **kwargs: Any, 124 ) -> None: 125 kwargs.update( 126 { 127 "style": style, 128 "text_style": text_style, 129 "draw_id": draw_id, 130 "layer": layer, 131 } 132 ) 133 super().__init__(**kwargs) 134 if self._do_init: 135 if p1: 136 self.x1 = p1[0] 137 self.y1 = p1[1] 138 if p2: 139 self.x2 = p2[0] 140 self.y2 = p2[1]
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
33class Link(ParagraphBase): 34 """Link class, "text:a" ODF element.""" 35 36 _tag = "text:a" 37 _properties: tuple[PropDef, ...] = ( 38 PropDef("url", "xlink:href"), 39 PropDef("name", "office:name"), 40 PropDef("title", "office:title"), 41 PropDef("target_frame", "office:target-frame-name"), 42 PropDef("show", "xlink:show"), 43 PropDef("visited_style", "text:visited-style-name"), 44 PropDef("style", "text:style-name"), 45 ) 46 47 def __init__( 48 self, 49 url: str | None = "", 50 name: str | None = None, 51 title: str | None = None, 52 text: str | None = None, 53 target_frame: str | None = None, 54 style: str | None = None, 55 visited_style: str | None = None, 56 **kwargs: Any, 57 ) -> None: 58 """ 59 Arguments: 60 61 url -- str 62 63 name -- str 64 65 title -- str 66 67 text -- str 68 69 target_frame -- '_self', '_blank', '_parent', '_top' 70 71 style -- str 72 73 visited_style -- str 74 """ 75 super().__init__(**kwargs) 76 if self._do_init: 77 self.url = url 78 if name is not None: 79 self.name = name 80 if title is not None: 81 self.title = title 82 if text is not None: 83 self.text = text 84 if target_frame is not None: 85 self.target_frame = target_frame 86 # show can be: 'new' or 'replace'" 87 if target_frame == "_blank": 88 self.show = "new" 89 else: 90 self.show = "replace" 91 if style is not None: 92 self.style = style 93 if visited_style is not None: 94 self.visited_style = visited_style 95 96 def __repr__(self) -> str: 97 return f"<{self.__class__.__name__} tag={self.tag} link={self.url}>" 98 99 def __str__(self) -> str: 100 if self.name: 101 return f"[{self.name}]({self.url})" 102 return f"({self.url})"
Link class, "text:a" ODF element.
47 def __init__( 48 self, 49 url: str | None = "", 50 name: str | None = None, 51 title: str | None = None, 52 text: str | None = None, 53 target_frame: str | None = None, 54 style: str | None = None, 55 visited_style: str | None = None, 56 **kwargs: Any, 57 ) -> None: 58 """ 59 Arguments: 60 61 url -- str 62 63 name -- str 64 65 title -- str 66 67 text -- str 68 69 target_frame -- '_self', '_blank', '_parent', '_top' 70 71 style -- str 72 73 visited_style -- str 74 """ 75 super().__init__(**kwargs) 76 if self._do_init: 77 self.url = url 78 if name is not None: 79 self.name = name 80 if title is not None: 81 self.title = title 82 if text is not None: 83 self.text = text 84 if target_frame is not None: 85 self.target_frame = target_frame 86 # show can be: 'new' or 'replace'" 87 if target_frame == "_blank": 88 self.show = "new" 89 else: 90 self.show = "replace" 91 if style is not None: 92 self.style = style 93 if visited_style is not None: 94 self.visited_style = visited_style
Arguments:
url -- str
name -- str
title -- str
text -- str
target_frame -- '_self', '_blank', '_parent', '_top'
style -- str
visited_style -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
68class List(Element): 69 """ODF List "text:list".""" 70 71 _tag = "text:list" 72 _properties = (PropDef("style", "text:style-name"),) 73 74 def __init__( 75 self, 76 list_content: str | Element | Iterable[str | Element] | None = None, 77 style: str | None = None, 78 **kwargs: Any, 79 ) -> None: 80 """Create a list element, optionaly loading the list by a list of 81 item (str or elements). 82 83 The list_content argument is just a shortcut for the most common case. 84 To create more complex lists, first create an empty list, and fill it 85 afterwards. 86 87 Arguments: 88 89 list_content -- str or Element, or a list of str or Element 90 91 style -- str 92 """ 93 super().__init__(**kwargs) 94 if self._do_init: 95 if list_content: 96 if isinstance(list_content, (Element, str)): 97 self.append(ListItem(list_content)) 98 elif hasattr(list_content, "__iter__"): 99 for item in list_content: 100 self.append(ListItem(item)) 101 if style is not None: 102 self.style = style 103 104 def get_items(self, content: str | None = None) -> list[Element]: 105 """Return all the list items that match the criteria. 106 107 Arguments: 108 109 style -- str 110 111 content -- str regex 112 113 Return: list of Element 114 """ 115 return self._filtered_elements("text:list-item", content=content) 116 117 def get_item( 118 self, 119 position: int = 0, 120 content: str | None = None, 121 ) -> Element | None: 122 """Return the list item that matches the criteria. In nested lists, 123 return the list item that really contains that content. 124 125 Arguments: 126 127 position -- int 128 129 content -- str regex 130 131 Return: Element or None if not found 132 """ 133 # Custom implementation because of nested lists 134 if content: 135 # Don't search recursively but on the very own paragraph(s) of 136 # each list item 137 for paragraph in self.get_elements("descendant::text:p"): 138 if paragraph.match(content): 139 return paragraph.get_element("parent::text:list-item") 140 return None 141 return self._filtered_element("text:list-item", position) 142 143 def set_list_header( 144 self, 145 text_or_element: str | Element | Iterable[str | Element], 146 ) -> None: 147 if isinstance(text_or_element, (str, Element)): 148 actual_list: list[str | Element] | tuple = [text_or_element] 149 elif isinstance(text_or_element, (list, tuple)): 150 actual_list = text_or_element 151 else: 152 raise TypeError 153 # Remove existing header 154 for element in self.get_elements("text:p"): 155 self.delete(element) 156 for paragraph in reversed(actual_list): 157 if isinstance(paragraph, str): 158 paragraph = Paragraph(paragraph) 159 self.insert(paragraph, FIRST_CHILD) 160 161 def insert_item( 162 self, 163 item: ListItem | str | Element | None, 164 position: int | None = None, 165 before: Element | None = None, 166 after: Element | None = None, 167 ) -> None: 168 if not isinstance(item, ListItem): 169 item = ListItem(item) 170 if before is not None: 171 before.insert(item, xmlposition=PREV_SIBLING) 172 elif after is not None: 173 after.insert(item, xmlposition=NEXT_SIBLING) 174 elif position is not None: 175 self.insert(item, position=position) 176 else: 177 raise ValueError("Position must be defined") 178 179 def append_item( 180 self, 181 item: ListItem | str | Element | None, 182 ) -> None: 183 if not isinstance(item, ListItem): 184 item = ListItem(item) 185 self.append(item) 186 187 def get_formatted_text(self, context: dict | None = None) -> str: 188 if context is None: 189 context = {} 190 rst_mode = context["rst_mode"] 191 result = [] 192 if rst_mode: 193 result.append("\n") 194 for list_item in self.get_elements("text:list-item"): 195 textbuf = [] 196 for child in list_item.children: 197 text = child.get_formatted_text(context) 198 tag = child.tag 199 if tag == "text:h": 200 # A title in a list is a bug 201 return text 202 if tag == "text:list" and not text.lstrip().startswith("-"): 203 # If the list didn't indent, don't either 204 # (inner title) 205 return text 206 textbuf.append(text) 207 text_sum = "".join(textbuf) 208 text_sum = text_sum.strip("\n") 209 # Indent the text 210 text_sum = text_sum.replace("\n", "\n ") 211 text_sum = f"- {text_sum}\n" 212 result.append(text_sum) 213 if rst_mode: 214 result.append("\n") 215 return "".join(result)
ODF List "text:list".
74 def __init__( 75 self, 76 list_content: str | Element | Iterable[str | Element] | None = None, 77 style: str | None = None, 78 **kwargs: Any, 79 ) -> None: 80 """Create a list element, optionaly loading the list by a list of 81 item (str or elements). 82 83 The list_content argument is just a shortcut for the most common case. 84 To create more complex lists, first create an empty list, and fill it 85 afterwards. 86 87 Arguments: 88 89 list_content -- str or Element, or a list of str or Element 90 91 style -- str 92 """ 93 super().__init__(**kwargs) 94 if self._do_init: 95 if list_content: 96 if isinstance(list_content, (Element, str)): 97 self.append(ListItem(list_content)) 98 elif hasattr(list_content, "__iter__"): 99 for item in list_content: 100 self.append(ListItem(item)) 101 if style is not None: 102 self.style = style
Create a list element, optionaly loading the list by a list of item (str or elements).
The list_content argument is just a shortcut for the most common case. To create more complex lists, first create an empty list, and fill it afterwards.
Arguments:
list_content -- str or Element, or a list of str or Element
style -- str
104 def get_items(self, content: str | None = None) -> list[Element]: 105 """Return all the list items that match the criteria. 106 107 Arguments: 108 109 style -- str 110 111 content -- str regex 112 113 Return: list of Element 114 """ 115 return self._filtered_elements("text:list-item", content=content)
Return all the list items that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Element
117 def get_item( 118 self, 119 position: int = 0, 120 content: str | None = None, 121 ) -> Element | None: 122 """Return the list item that matches the criteria. In nested lists, 123 return the list item that really contains that content. 124 125 Arguments: 126 127 position -- int 128 129 content -- str regex 130 131 Return: Element or None if not found 132 """ 133 # Custom implementation because of nested lists 134 if content: 135 # Don't search recursively but on the very own paragraph(s) of 136 # each list item 137 for paragraph in self.get_elements("descendant::text:p"): 138 if paragraph.match(content): 139 return paragraph.get_element("parent::text:list-item") 140 return None 141 return self._filtered_element("text:list-item", position)
Return the list item that matches the criteria. In nested lists, return the list item that really contains that content.
Arguments:
position -- int
content -- str regex
Return: Element or None if not found
143 def set_list_header( 144 self, 145 text_or_element: str | Element | Iterable[str | Element], 146 ) -> None: 147 if isinstance(text_or_element, (str, Element)): 148 actual_list: list[str | Element] | tuple = [text_or_element] 149 elif isinstance(text_or_element, (list, tuple)): 150 actual_list = text_or_element 151 else: 152 raise TypeError 153 # Remove existing header 154 for element in self.get_elements("text:p"): 155 self.delete(element) 156 for paragraph in reversed(actual_list): 157 if isinstance(paragraph, str): 158 paragraph = Paragraph(paragraph) 159 self.insert(paragraph, FIRST_CHILD)
161 def insert_item( 162 self, 163 item: ListItem | str | Element | None, 164 position: int | None = None, 165 before: Element | None = None, 166 after: Element | None = None, 167 ) -> None: 168 if not isinstance(item, ListItem): 169 item = ListItem(item) 170 if before is not None: 171 before.insert(item, xmlposition=PREV_SIBLING) 172 elif after is not None: 173 after.insert(item, xmlposition=NEXT_SIBLING) 174 elif position is not None: 175 self.insert(item, position=position) 176 else: 177 raise ValueError("Position must be defined")
187 def get_formatted_text(self, context: dict | None = None) -> str: 188 if context is None: 189 context = {} 190 rst_mode = context["rst_mode"] 191 result = [] 192 if rst_mode: 193 result.append("\n") 194 for list_item in self.get_elements("text:list-item"): 195 textbuf = [] 196 for child in list_item.children: 197 text = child.get_formatted_text(context) 198 tag = child.tag 199 if tag == "text:h": 200 # A title in a list is a bug 201 return text 202 if tag == "text:list" and not text.lstrip().startswith("-"): 203 # If the list didn't indent, don't either 204 # (inner title) 205 return text 206 textbuf.append(text) 207 text_sum = "".join(textbuf) 208 text_sum = text_sum.strip("\n") 209 # Indent the text 210 text_sum = text_sum.replace("\n", "\n ") 211 text_sum = f"- {text_sum}\n" 212 result.append(text_sum) 213 if rst_mode: 214 result.append("\n") 215 return "".join(result)
This function should return a beautiful version of the text.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
41class ListItem(Element): 42 """ODF element "text:list-item", item of a List.""" 43 44 _tag = "text:list-item" 45 46 def __init__( 47 self, 48 text_or_element: str | Element | None = None, 49 **kwargs: Any, 50 ) -> None: 51 """Create a list item element, optionaly passing at creation time a 52 string or Element as content. 53 54 Arguments: 55 56 text_or_element -- str or ODF Element 57 """ 58 super().__init__(**kwargs) 59 if self._do_init: 60 if isinstance(text_or_element, str): 61 self.text_content = text_or_element 62 elif isinstance(text_or_element, Element): 63 self.append(text_or_element) 64 elif text_or_element is not None: 65 raise TypeError("Expected str or Element")
ODF element "text:list-item", item of a List.
46 def __init__( 47 self, 48 text_or_element: str | Element | None = None, 49 **kwargs: Any, 50 ) -> None: 51 """Create a list item element, optionaly passing at creation time a 52 string or Element as content. 53 54 Arguments: 55 56 text_or_element -- str or ODF Element 57 """ 58 super().__init__(**kwargs) 59 if self._do_init: 60 if isinstance(text_or_element, str): 61 self.text_content = text_or_element 62 elif isinstance(text_or_element, Element): 63 self.append(text_or_element) 64 elif text_or_element is not None: 65 raise TypeError("Expected str or Element")
Create a list item element, optionaly passing at creation time a string or Element as content.
Arguments:
text_or_element -- str or ODF Element
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
31class Manifest(XmlPart): 32 def get_paths(self) -> list[Element | Text]: 33 """Return the list of full paths in the manifest. 34 35 Return: list of str 36 """ 37 xpath_query = "//manifest:file-entry/attribute::manifest:full-path" 38 return self.xpath(xpath_query) 39 40 def _file_entry(self, full_path: str) -> Element: 41 xpath_query = ( 42 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 43 ) 44 result = self.xpath(xpath_query) 45 if not result: 46 raise KeyError(f"Path not found: '{full_path}'") 47 return result[0] # type: ignore 48 49 def get_path_medias(self) -> list[tuple]: 50 """Return the list of (full_path, media_type) pairs in the manifest. 51 52 Return: list of str tuples 53 """ 54 xpath_query = "//manifest:file-entry" 55 result = [] 56 for file_entry in self.xpath(xpath_query): 57 if not isinstance(file_entry, Element): 58 continue 59 result.append( 60 ( 61 file_entry.get_attribute_string("manifest:full-path"), 62 file_entry.get_attribute_string("manifest:media-type"), 63 ) 64 ) 65 return result 66 67 def get_media_type(self, full_path: str) -> str | None: 68 """Get the media type of an existing path. 69 70 Return: str 71 """ 72 xpath_query = ( 73 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 74 "/attribute::manifest:media-type" 75 ) 76 result = self.xpath(xpath_query) 77 if not result: 78 return None 79 return str(result[0]) 80 81 def set_media_type(self, full_path: str, media_type: str) -> None: 82 """Set the media type of an existing path. 83 84 Arguments: 85 86 full_path -- str 87 88 media_type -- str 89 """ 90 file_entry = self._file_entry(full_path) 91 file_entry.set_attribute("manifest:media-type", media_type) 92 93 @staticmethod 94 def make_file_entry(full_path: str, media_type: str) -> Element: 95 tag = ( 96 f"<manifest:file-entry " 97 f'manifest:media-type="{media_type}" ' 98 f'manifest:full-path="{full_path}"/>' 99 ) 100 return Element.from_tag(tag) 101 102 def add_full_path(self, full_path: str, media_type: str = "") -> None: 103 # Existing? 104 existing = self.get_media_type(full_path) 105 if existing is not None: 106 self.set_media_type(full_path, media_type) 107 root = self.root 108 root.append(self.make_file_entry(full_path, media_type)) 109 110 def del_full_path(self, full_path: str) -> None: 111 file_entry = self._file_entry(full_path) 112 self.root.delete(file_entry)
Representation of an XML part.
Abstraction of the XML library behind.
32 def get_paths(self) -> list[Element | Text]: 33 """Return the list of full paths in the manifest. 34 35 Return: list of str 36 """ 37 xpath_query = "//manifest:file-entry/attribute::manifest:full-path" 38 return self.xpath(xpath_query)
Return the list of full paths in the manifest.
Return: list of str
49 def get_path_medias(self) -> list[tuple]: 50 """Return the list of (full_path, media_type) pairs in the manifest. 51 52 Return: list of str tuples 53 """ 54 xpath_query = "//manifest:file-entry" 55 result = [] 56 for file_entry in self.xpath(xpath_query): 57 if not isinstance(file_entry, Element): 58 continue 59 result.append( 60 ( 61 file_entry.get_attribute_string("manifest:full-path"), 62 file_entry.get_attribute_string("manifest:media-type"), 63 ) 64 ) 65 return result
Return the list of (full_path, media_type) pairs in the manifest.
Return: list of str tuples
67 def get_media_type(self, full_path: str) -> str | None: 68 """Get the media type of an existing path. 69 70 Return: str 71 """ 72 xpath_query = ( 73 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 74 "/attribute::manifest:media-type" 75 ) 76 result = self.xpath(xpath_query) 77 if not result: 78 return None 79 return str(result[0])
Get the media type of an existing path.
Return: str
81 def set_media_type(self, full_path: str, media_type: str) -> None: 82 """Set the media type of an existing path. 83 84 Arguments: 85 86 full_path -- str 87 88 media_type -- str 89 """ 90 file_entry = self._file_entry(full_path) 91 file_entry.set_attribute("manifest:media-type", media_type)
Set the media type of an existing path.
Arguments:
full_path -- str
media_type -- str
Inherited Members
43class Meta(XmlPart): 44 def __init__(self, *args: Any, **kwargs: Any) -> None: 45 super().__init__(*args, **kwargs) 46 self._generator_modified: bool = False 47 48 def get_meta_body(self) -> Element: 49 return self.get_element("//office:meta") 50 51 def get_title(self) -> str | None: 52 """Get the title of the document. 53 54 This is not the first heading but the title metadata. 55 56 Return: str (or None if inexistant) 57 """ 58 element = self.get_element("//dc:title") 59 if element is None: 60 return None 61 return element.text 62 63 def set_title(self, title: str) -> None: 64 """Set the title of the document. 65 66 This is not the first heading but the title metadata. 67 68 Arguments: 69 70 title -- str 71 """ 72 element = self.get_element("//dc:title") 73 if element is None: 74 element = Element.from_tag("dc:title") 75 self.get_meta_body().append(element) 76 element.text = title 77 78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text 87 88 # As named in OOo 89 get_comments = get_description 90 91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description 103 104 set_comments = set_description 105 106 def get_subject(self) -> str | None: 107 """Get the subject of the document. 108 109 Return: str (or None if inexistant) 110 """ 111 element = self.get_element("//dc:subject") 112 if element is None: 113 return None 114 return element.text 115 116 def set_subject(self, subject: str) -> None: 117 """Set the subject of the document. 118 119 Arguments: 120 121 subject -- str 122 """ 123 element = self.get_element("//dc:subject") 124 if element is None: 125 element = Element.from_tag("dc:subject") 126 self.get_meta_body().append(element) 127 element.text = subject 128 129 def get_language(self) -> str | None: 130 """Get the language code of the document. 131 132 Return: str (or None if inexistant) 133 134 Example:: 135 136 >>> document.meta.get_language() 137 fr-FR 138 """ 139 element = self.get_element("//dc:language") 140 if element is None: 141 return None 142 return element.text 143 144 def set_language(self, language: str) -> None: 145 """Set the language code of the document. 146 147 Arguments: 148 149 language -- str 150 151 Example:: 152 153 >>> document.meta.set_language('fr-FR') 154 """ 155 language = str(language) 156 if not self._is_RFC3066(language): 157 raise TypeError( 158 'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)' 159 ) 160 element = self.get_element("//dc:language") 161 if element is None: 162 element = Element.from_tag("dc:language") 163 self.get_meta_body().append(element) 164 element.text = language 165 166 @staticmethod 167 def _is_RFC3066(lang: str) -> bool: 168 def test_part1(part1: str) -> bool: 169 if not 2 <= len(part1) <= 3: 170 return False 171 return all(x in ascii_letters for x in part1) 172 173 def test_part2(part2: str) -> bool: 174 return all(x in ascii_letters or x in digits for x in part2) 175 176 if not lang or not isinstance(lang, str): 177 return False 178 if "-" not in lang: 179 return test_part1(lang) 180 parts = lang.split("-") 181 if len(parts) > 3: 182 return False 183 if not test_part1(parts[0]): 184 return False 185 return all(test_part2(p) for p in parts[1:]) 186 187 def get_modification_date(self) -> datetime | None: 188 """Get the last modified date of the document. 189 190 Return: datetime (or None if inexistant) 191 """ 192 element = self.get_element("//dc:date") 193 if element is None: 194 return None 195 modification_date = element.text 196 return DateTime.decode(modification_date) 197 198 def set_modification_date(self, date: datetime) -> None: 199 """Set the last modified date of the document. 200 201 Arguments: 202 203 date -- datetime 204 """ 205 element = self.get_element("//dc:date") 206 if element is None: 207 element = Element.from_tag("dc:date") 208 self.get_meta_body().append(element) 209 element.text = DateTime.encode(date) 210 211 def get_creation_date(self) -> datetime | None: 212 """Get the creation date of the document. 213 214 Return: datetime (or None if inexistant) 215 """ 216 element = self.get_element("//meta:creation-date") 217 if element is None: 218 return None 219 creation_date = element.text 220 return DateTime.decode(creation_date) 221 222 def set_creation_date(self, date: datetime) -> None: 223 """Set the creation date of the document. 224 225 Arguments: 226 227 date -- datetime 228 """ 229 element = self.get_element("//meta:creation-date") 230 if element is None: 231 element = Element.from_tag("meta:creation-date") 232 self.get_meta_body().append(element) 233 element.text = DateTime.encode(date) 234 235 def get_initial_creator(self) -> str | None: 236 """Get the first creator of the document. 237 238 Return: str (or None if inexistant) 239 240 Example:: 241 242 >>> document.meta.get_initial_creator() 243 Unknown 244 """ 245 element = self.get_element("//meta:initial-creator") 246 if element is None: 247 return None 248 return element.text 249 250 def set_initial_creator(self, creator: str) -> None: 251 """Set the first creator of the document. 252 253 Arguments: 254 255 creator -- str 256 257 Example:: 258 259 >>> document.meta.set_initial_creator("Plato") 260 """ 261 element = self.get_element("//meta:initial-creator") 262 if element is None: 263 element = Element.from_tag("meta:initial-creator") 264 self.get_meta_body().append(element) 265 element.text = creator 266 267 def get_creator(self) -> str | None: 268 """Get the creator of the document. 269 270 Return: str (or None if inexistant) 271 272 Example:: 273 274 >>> document.meta.get_creator() 275 Unknown 276 """ 277 element = self.get_element("//dc:creator") 278 if element is None: 279 return None 280 return element.text 281 282 def set_creator(self, creator: str) -> None: 283 """Set the creator of the document. 284 285 Arguments: 286 287 creator -- str 288 289 Example:: 290 291 >>> document.meta.set_creator("Plato") 292 """ 293 element = self.get_element("//dc:creator") 294 if element is None: 295 element = Element.from_tag("dc:creator") 296 self.get_meta_body().append(element) 297 element.text = creator 298 299 def get_keywords(self) -> str | None: 300 """Get the keywords of the document. Return the field as-is, without 301 any assumption on the keyword separator. 302 303 Return: str (or None if inexistant) 304 """ 305 element = self.get_element("//meta:keyword") 306 if element is None: 307 return None 308 return element.text 309 310 def set_keywords(self, keywords: str) -> None: 311 """Set the keywords of the document. Although the name is plural, a 312 str string is required, so join your list first. 313 314 Arguments: 315 316 keywords -- str 317 """ 318 element = self.get_element("//meta:keyword") 319 if element is None: 320 element = Element.from_tag("meta:keyword") 321 self.get_meta_body().append(element) 322 element.text = keywords 323 324 def get_editing_duration(self) -> timedelta | None: 325 """Get the time the document was edited, as reported by the 326 generator. 327 328 Return: timedelta (or None if inexistant) 329 """ 330 element = self.get_element("//meta:editing-duration") 331 if element is None: 332 return None 333 duration = element.text 334 return Duration.decode(duration) 335 336 def set_editing_duration(self, duration: timedelta) -> None: 337 """Set the time the document was edited. 338 339 Arguments: 340 341 duration -- timedelta 342 """ 343 if not isinstance(duration, timedelta): 344 raise TypeError("duration must be a timedelta") 345 element = self.get_element("//meta:editing-duration") 346 if element is None: 347 element = Element.from_tag("meta:editing-duration") 348 self.get_meta_body().append(element) 349 element.text = Duration.encode(duration) 350 351 def get_editing_cycles(self) -> int | None: 352 """Get the number of times the document was edited, as reported by 353 the generator. 354 355 Return: int (or None if inexistant) 356 """ 357 element = self.get_element("//meta:editing-cycles") 358 if element is None: 359 return None 360 cycles = element.text 361 return int(cycles) 362 363 def set_editing_cycles(self, cycles: int) -> None: 364 """Set the number of times the document was edited. 365 366 Arguments: 367 368 cycles -- int 369 """ 370 if not isinstance(cycles, int): 371 raise TypeError("cycles must be an int") 372 if cycles < 1: 373 raise ValueError("cycles must be a positive int") 374 element = self.get_element("//meta:editing-cycles") 375 if element is None: 376 element = Element.from_tag("meta:editing-cycles") 377 self.get_meta_body().append(element) 378 element.text = str(cycles) 379 380 def get_generator(self) -> str | None: 381 """Get the signature of the software that generated this document. 382 383 Return: str (or None if inexistant) 384 385 Example:: 386 387 >>> document.meta.get_generator() 388 KOffice/2.0.0 389 """ 390 element = self.get_element("//meta:generator") 391 if element is None: 392 return None 393 return element.text 394 395 def set_generator(self, generator: str) -> None: 396 """Set the signature of the software that generated this document. 397 398 Arguments: 399 400 generator -- str 401 402 Example:: 403 404 >>> document.meta.set_generator("Odfdo experiment") 405 """ 406 element = self.get_element("//meta:generator") 407 if element is None: 408 element = Element.from_tag("meta:generator") 409 self.get_meta_body().append(element) 410 element.text = generator 411 self._generator_modified = True 412 413 def set_generator_default(self) -> None: 414 """Set the signature of the software that generated this document 415 to ourself. 416 417 Example:: 418 419 >>> document.meta.set_generator_default() 420 """ 421 if not self._generator_modified: 422 self.set_generator(GENERATOR) 423 424 def get_statistic(self) -> dict[str, int] | None: 425 """Get the statistic from the software that generated this document. 426 427 Return: dict (or None if inexistant) 428 429 Example:: 430 431 >>> document.get_statistic(): 432 {'meta:table-count': 1, 433 'meta:image-count': 2, 434 'meta:object-count': 3, 435 'meta:page-count': 4, 436 'meta:paragraph-count': 5, 437 'meta:word-count': 6, 438 'meta:character-count': 7} 439 """ 440 element = self.get_element("//meta:document-statistic") 441 if element is None: 442 return None 443 statistic = {} 444 for key, value in element.attributes.items(): 445 statistic[to_str(key)] = int(value) 446 return statistic 447 448 def set_statistic(self, statistic: dict[str, int]) -> None: 449 """Set the statistic for the documents: number of words, paragraphs, 450 etc. 451 452 Arguments: 453 454 statistic -- dict 455 456 Example:: 457 458 >>> statistic = {'meta:table-count': 1, 459 'meta:image-count': 2, 460 'meta:object-count': 3, 461 'meta:page-count': 4, 462 'meta:paragraph-count': 5, 463 'meta:word-count': 6, 464 'meta:character-count': 7} 465 >>> document.meta.set_statistic(statistic) 466 """ 467 if not isinstance(statistic, dict): 468 raise TypeError("Statistic must be a dict") 469 element = self.get_element("//meta:document-statistic") 470 for key, value in statistic.items(): 471 try: 472 ivalue = int(value) 473 except ValueError as e: 474 raise TypeError("Statistic value must be a int") from e 475 element.set_attribute(to_str(key), str(ivalue)) 476 477 def get_user_defined_metadata(self) -> dict[str, Any]: 478 """Return a dict of str/value mapping. 479 480 Value types can be: Decimal, date, time, boolean or str. 481 """ 482 result: dict[str, Any] = {} 483 for item in self.get_elements("//meta:user-defined"): 484 if not isinstance(item, Element): 485 continue 486 # Read the values 487 name = item.get_attribute_string("meta:name") 488 if name is None: 489 continue 490 value = self._get_meta_value(item) 491 result[name] = value 492 return result 493 494 def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None: 495 """Return the content of the user defined metadata of that name. 496 Return None if no name matchs or a dic of fields. 497 498 Arguments: 499 500 name -- string, name (meta:name content) 501 """ 502 result = {} 503 found = False 504 for item in self.get_elements("//meta:user-defined"): 505 if not isinstance(item, Element): 506 continue 507 # Read the values 508 name = item.get_attribute("meta:name") 509 if name == keyname: 510 found = True 511 break 512 if not found: 513 return None 514 result["name"] = name 515 value, value_type, text = self._get_meta_value(item, full=True) # type: ignore 516 result["value"] = value 517 result["value_type"] = value_type 518 result["text"] = text 519 return result 520 521 def set_user_defined_metadata(self, name: str, value: Any) -> None: 522 if isinstance(value, bool): 523 value_type = "boolean" 524 value = "true" if value else "false" 525 elif isinstance(value, (int, float, Decimal)): 526 value_type = "float" 527 value = str(value) 528 elif isinstance(value, dtdate): 529 value_type = "date" 530 value = str(Date.encode(value)) 531 elif isinstance(value, datetime): 532 value_type = "date" 533 value = str(DateTime.encode(value)) 534 elif isinstance(value, str): 535 value_type = "string" 536 elif isinstance(value, timedelta): 537 value_type = "time" 538 value = str(Duration.encode(value)) 539 else: 540 raise TypeError('unexpected type "%s" for value' % type(value)) 541 # Already the same element ? 542 for metadata in self.get_elements("//meta:user-defined"): 543 if not isinstance(metadata, Element): 544 continue 545 if metadata.get_attribute("meta:name") == name: 546 break 547 else: 548 metadata = Element.from_tag("meta:user-defined") 549 metadata.set_attribute("meta:name", name) 550 self.get_meta_body().append(metadata) 551 metadata.set_attribute("meta:value-type", value_type) 552 metadata.text = value 553 554 def _get_meta_value( 555 self, element: Element, full: bool = False 556 ) -> Any | tuple[Any, str, str]: 557 """get_value() deicated to the meta data part, for one meta element.""" 558 if full: 559 return self._get_meta_value_full(element) 560 else: 561 return self._get_meta_value_full(element)[0] 562 563 @staticmethod 564 def _get_meta_value_full(element: Element) -> tuple[Any, str, str]: 565 """get_value deicated to the meta data part, for one meta element.""" 566 # name = element.get_attribute('meta:name') 567 value_type = element.get_attribute_string("meta:value-type") 568 if value_type is None: 569 value_type = "string" 570 text = element.text 571 # Interpretation 572 if value_type == "boolean": 573 return (Boolean.decode(text), value_type, text) 574 if value_type in ("float", "percentage", "currency"): 575 return (Decimal(text), value_type, text) 576 if value_type == "date": 577 if "T" in text: 578 return (DateTime.decode(text), value_type, text) 579 else: 580 return (Date.decode(text), value_type, text) 581 if value_type == "string": 582 return (text, value_type, text) 583 if value_type == "time": 584 return (Duration.decode(text), value_type, text) 585 raise TypeError(f"Unknown value type: '{value_type!r}'")
Representation of an XML part.
Abstraction of the XML library behind.
51 def get_title(self) -> str | None: 52 """Get the title of the document. 53 54 This is not the first heading but the title metadata. 55 56 Return: str (or None if inexistant) 57 """ 58 element = self.get_element("//dc:title") 59 if element is None: 60 return None 61 return element.text
Get the title of the document.
This is not the first heading but the title metadata.
Return: str (or None if inexistant)
63 def set_title(self, title: str) -> None: 64 """Set the title of the document. 65 66 This is not the first heading but the title metadata. 67 68 Arguments: 69 70 title -- str 71 """ 72 element = self.get_element("//dc:title") 73 if element is None: 74 element = Element.from_tag("dc:title") 75 self.get_meta_body().append(element) 76 element.text = title
Set the title of the document.
This is not the first heading but the title metadata.
Arguments:
title -- str
78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text
Get the description of the document. Also known as comments.
Return: str (or None if inexistant)
78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text
Get the description of the document. Also known as comments.
Return: str (or None if inexistant)
91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description
Set the description of the document. Also known as comments.
Arguments:
description -- str
91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description
Set the description of the document. Also known as comments.
Arguments:
description -- str
106 def get_subject(self) -> str | None: 107 """Get the subject of the document. 108 109 Return: str (or None if inexistant) 110 """ 111 element = self.get_element("//dc:subject") 112 if element is None: 113 return None 114 return element.text
Get the subject of the document.
Return: str (or None if inexistant)
116 def set_subject(self, subject: str) -> None: 117 """Set the subject of the document. 118 119 Arguments: 120 121 subject -- str 122 """ 123 element = self.get_element("//dc:subject") 124 if element is None: 125 element = Element.from_tag("dc:subject") 126 self.get_meta_body().append(element) 127 element.text = subject
Set the subject of the document.
Arguments:
subject -- str
129 def get_language(self) -> str | None: 130 """Get the language code of the document. 131 132 Return: str (or None if inexistant) 133 134 Example:: 135 136 >>> document.meta.get_language() 137 fr-FR 138 """ 139 element = self.get_element("//dc:language") 140 if element is None: 141 return None 142 return element.text
Get the language code of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_language()
fr-FR
144 def set_language(self, language: str) -> None: 145 """Set the language code of the document. 146 147 Arguments: 148 149 language -- str 150 151 Example:: 152 153 >>> document.meta.set_language('fr-FR') 154 """ 155 language = str(language) 156 if not self._is_RFC3066(language): 157 raise TypeError( 158 'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)' 159 ) 160 element = self.get_element("//dc:language") 161 if element is None: 162 element = Element.from_tag("dc:language") 163 self.get_meta_body().append(element) 164 element.text = language
Set the language code of the document.
Arguments:
language -- str
Example::
>>> document.meta.set_language('fr-FR')
187 def get_modification_date(self) -> datetime | None: 188 """Get the last modified date of the document. 189 190 Return: datetime (or None if inexistant) 191 """ 192 element = self.get_element("//dc:date") 193 if element is None: 194 return None 195 modification_date = element.text 196 return DateTime.decode(modification_date)
Get the last modified date of the document.
Return: datetime (or None if inexistant)
198 def set_modification_date(self, date: datetime) -> None: 199 """Set the last modified date of the document. 200 201 Arguments: 202 203 date -- datetime 204 """ 205 element = self.get_element("//dc:date") 206 if element is None: 207 element = Element.from_tag("dc:date") 208 self.get_meta_body().append(element) 209 element.text = DateTime.encode(date)
Set the last modified date of the document.
Arguments:
date -- datetime
211 def get_creation_date(self) -> datetime | None: 212 """Get the creation date of the document. 213 214 Return: datetime (or None if inexistant) 215 """ 216 element = self.get_element("//meta:creation-date") 217 if element is None: 218 return None 219 creation_date = element.text 220 return DateTime.decode(creation_date)
Get the creation date of the document.
Return: datetime (or None if inexistant)
222 def set_creation_date(self, date: datetime) -> None: 223 """Set the creation date of the document. 224 225 Arguments: 226 227 date -- datetime 228 """ 229 element = self.get_element("//meta:creation-date") 230 if element is None: 231 element = Element.from_tag("meta:creation-date") 232 self.get_meta_body().append(element) 233 element.text = DateTime.encode(date)
Set the creation date of the document.
Arguments:
date -- datetime
235 def get_initial_creator(self) -> str | None: 236 """Get the first creator of the document. 237 238 Return: str (or None if inexistant) 239 240 Example:: 241 242 >>> document.meta.get_initial_creator() 243 Unknown 244 """ 245 element = self.get_element("//meta:initial-creator") 246 if element is None: 247 return None 248 return element.text
Get the first creator of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_initial_creator()
Unknown
250 def set_initial_creator(self, creator: str) -> None: 251 """Set the first creator of the document. 252 253 Arguments: 254 255 creator -- str 256 257 Example:: 258 259 >>> document.meta.set_initial_creator("Plato") 260 """ 261 element = self.get_element("//meta:initial-creator") 262 if element is None: 263 element = Element.from_tag("meta:initial-creator") 264 self.get_meta_body().append(element) 265 element.text = creator
Set the first creator of the document.
Arguments:
creator -- str
Example::
>>> document.meta.set_initial_creator("Plato")
267 def get_creator(self) -> str | None: 268 """Get the creator of the document. 269 270 Return: str (or None if inexistant) 271 272 Example:: 273 274 >>> document.meta.get_creator() 275 Unknown 276 """ 277 element = self.get_element("//dc:creator") 278 if element is None: 279 return None 280 return element.text
Get the creator of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_creator()
Unknown
282 def set_creator(self, creator: str) -> None: 283 """Set the creator of the document. 284 285 Arguments: 286 287 creator -- str 288 289 Example:: 290 291 >>> document.meta.set_creator("Plato") 292 """ 293 element = self.get_element("//dc:creator") 294 if element is None: 295 element = Element.from_tag("dc:creator") 296 self.get_meta_body().append(element) 297 element.text = creator
Set the creator of the document.
Arguments:
creator -- str
Example::
>>> document.meta.set_creator("Plato")
299 def get_keywords(self) -> str | None: 300 """Get the keywords of the document. Return the field as-is, without 301 any assumption on the keyword separator. 302 303 Return: str (or None if inexistant) 304 """ 305 element = self.get_element("//meta:keyword") 306 if element is None: 307 return None 308 return element.text
Get the keywords of the document. Return the field as-is, without any assumption on the keyword separator.
Return: str (or None if inexistant)
310 def set_keywords(self, keywords: str) -> None: 311 """Set the keywords of the document. Although the name is plural, a 312 str string is required, so join your list first. 313 314 Arguments: 315 316 keywords -- str 317 """ 318 element = self.get_element("//meta:keyword") 319 if element is None: 320 element = Element.from_tag("meta:keyword") 321 self.get_meta_body().append(element) 322 element.text = keywords
Set the keywords of the document. Although the name is plural, a str string is required, so join your list first.
Arguments:
keywords -- str
324 def get_editing_duration(self) -> timedelta | None: 325 """Get the time the document was edited, as reported by the 326 generator. 327 328 Return: timedelta (or None if inexistant) 329 """ 330 element = self.get_element("//meta:editing-duration") 331 if element is None: 332 return None 333 duration = element.text 334 return Duration.decode(duration)
Get the time the document was edited, as reported by the generator.
Return: timedelta (or None if inexistant)
336 def set_editing_duration(self, duration: timedelta) -> None: 337 """Set the time the document was edited. 338 339 Arguments: 340 341 duration -- timedelta 342 """ 343 if not isinstance(duration, timedelta): 344 raise TypeError("duration must be a timedelta") 345 element = self.get_element("//meta:editing-duration") 346 if element is None: 347 element = Element.from_tag("meta:editing-duration") 348 self.get_meta_body().append(element) 349 element.text = Duration.encode(duration)
Set the time the document was edited.
Arguments:
duration -- timedelta
351 def get_editing_cycles(self) -> int | None: 352 """Get the number of times the document was edited, as reported by 353 the generator. 354 355 Return: int (or None if inexistant) 356 """ 357 element = self.get_element("//meta:editing-cycles") 358 if element is None: 359 return None 360 cycles = element.text 361 return int(cycles)
Get the number of times the document was edited, as reported by the generator.
Return: int (or None if inexistant)
363 def set_editing_cycles(self, cycles: int) -> None: 364 """Set the number of times the document was edited. 365 366 Arguments: 367 368 cycles -- int 369 """ 370 if not isinstance(cycles, int): 371 raise TypeError("cycles must be an int") 372 if cycles < 1: 373 raise ValueError("cycles must be a positive int") 374 element = self.get_element("//meta:editing-cycles") 375 if element is None: 376 element = Element.from_tag("meta:editing-cycles") 377 self.get_meta_body().append(element) 378 element.text = str(cycles)
Set the number of times the document was edited.
Arguments:
cycles -- int
380 def get_generator(self) -> str | None: 381 """Get the signature of the software that generated this document. 382 383 Return: str (or None if inexistant) 384 385 Example:: 386 387 >>> document.meta.get_generator() 388 KOffice/2.0.0 389 """ 390 element = self.get_element("//meta:generator") 391 if element is None: 392 return None 393 return element.text
Get the signature of the software that generated this document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_generator()
KOffice/2.0.0
395 def set_generator(self, generator: str) -> None: 396 """Set the signature of the software that generated this document. 397 398 Arguments: 399 400 generator -- str 401 402 Example:: 403 404 >>> document.meta.set_generator("Odfdo experiment") 405 """ 406 element = self.get_element("//meta:generator") 407 if element is None: 408 element = Element.from_tag("meta:generator") 409 self.get_meta_body().append(element) 410 element.text = generator 411 self._generator_modified = True
Set the signature of the software that generated this document.
Arguments:
generator -- str
Example::
>>> document.meta.set_generator("Odfdo experiment")
413 def set_generator_default(self) -> None: 414 """Set the signature of the software that generated this document 415 to ourself. 416 417 Example:: 418 419 >>> document.meta.set_generator_default() 420 """ 421 if not self._generator_modified: 422 self.set_generator(GENERATOR)
Set the signature of the software that generated this document to ourself.
Example::
>>> document.meta.set_generator_default()
424 def get_statistic(self) -> dict[str, int] | None: 425 """Get the statistic from the software that generated this document. 426 427 Return: dict (or None if inexistant) 428 429 Example:: 430 431 >>> document.get_statistic(): 432 {'meta:table-count': 1, 433 'meta:image-count': 2, 434 'meta:object-count': 3, 435 'meta:page-count': 4, 436 'meta:paragraph-count': 5, 437 'meta:word-count': 6, 438 'meta:character-count': 7} 439 """ 440 element = self.get_element("//meta:document-statistic") 441 if element is None: 442 return None 443 statistic = {} 444 for key, value in element.attributes.items(): 445 statistic[to_str(key)] = int(value) 446 return statistic
Get the statistic from the software that generated this document.
Return: dict (or None if inexistant)
Example::
>>> document.get_statistic():
{'meta:table-count': 1,
'meta:image-count': 2,
'meta:object-count': 3,
'meta:page-count': 4,
'meta:paragraph-count': 5,
'meta:word-count': 6,
'meta:character-count': 7}
448 def set_statistic(self, statistic: dict[str, int]) -> None: 449 """Set the statistic for the documents: number of words, paragraphs, 450 etc. 451 452 Arguments: 453 454 statistic -- dict 455 456 Example:: 457 458 >>> statistic = {'meta:table-count': 1, 459 'meta:image-count': 2, 460 'meta:object-count': 3, 461 'meta:page-count': 4, 462 'meta:paragraph-count': 5, 463 'meta:word-count': 6, 464 'meta:character-count': 7} 465 >>> document.meta.set_statistic(statistic) 466 """ 467 if not isinstance(statistic, dict): 468 raise TypeError("Statistic must be a dict") 469 element = self.get_element("//meta:document-statistic") 470 for key, value in statistic.items(): 471 try: 472 ivalue = int(value) 473 except ValueError as e: 474 raise TypeError("Statistic value must be a int") from e 475 element.set_attribute(to_str(key), str(ivalue))
Set the statistic for the documents: number of words, paragraphs, etc.
Arguments:
statistic -- dict
Example::
>>> statistic = {'meta:table-count': 1,
'meta:image-count': 2,
'meta:object-count': 3,
'meta:page-count': 4,
'meta:paragraph-count': 5,
'meta:word-count': 6,
'meta:character-count': 7}
>>> document.meta.set_statistic(statistic)
477 def get_user_defined_metadata(self) -> dict[str, Any]: 478 """Return a dict of str/value mapping. 479 480 Value types can be: Decimal, date, time, boolean or str. 481 """ 482 result: dict[str, Any] = {} 483 for item in self.get_elements("//meta:user-defined"): 484 if not isinstance(item, Element): 485 continue 486 # Read the values 487 name = item.get_attribute_string("meta:name") 488 if name is None: 489 continue 490 value = self._get_meta_value(item) 491 result[name] = value 492 return result
Return a dict of str/value mapping.
Value types can be: Decimal, date, time, boolean or str.
494 def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None: 495 """Return the content of the user defined metadata of that name. 496 Return None if no name matchs or a dic of fields. 497 498 Arguments: 499 500 name -- string, name (meta:name content) 501 """ 502 result = {} 503 found = False 504 for item in self.get_elements("//meta:user-defined"): 505 if not isinstance(item, Element): 506 continue 507 # Read the values 508 name = item.get_attribute("meta:name") 509 if name == keyname: 510 found = True 511 break 512 if not found: 513 return None 514 result["name"] = name 515 value, value_type, text = self._get_meta_value(item, full=True) # type: ignore 516 result["value"] = value 517 result["value_type"] = value_type 518 result["text"] = text 519 return result
Return the content of the user defined metadata of that name. Return None if no name matchs or a dic of fields.
Arguments:
name -- string, name (meta:name content)
521 def set_user_defined_metadata(self, name: str, value: Any) -> None: 522 if isinstance(value, bool): 523 value_type = "boolean" 524 value = "true" if value else "false" 525 elif isinstance(value, (int, float, Decimal)): 526 value_type = "float" 527 value = str(value) 528 elif isinstance(value, dtdate): 529 value_type = "date" 530 value = str(Date.encode(value)) 531 elif isinstance(value, datetime): 532 value_type = "date" 533 value = str(DateTime.encode(value)) 534 elif isinstance(value, str): 535 value_type = "string" 536 elif isinstance(value, timedelta): 537 value_type = "time" 538 value = str(Duration.encode(value)) 539 else: 540 raise TypeError('unexpected type "%s" for value' % type(value)) 541 # Already the same element ? 542 for metadata in self.get_elements("//meta:user-defined"): 543 if not isinstance(metadata, Element): 544 continue 545 if metadata.get_attribute("meta:name") == name: 546 break 547 else: 548 metadata = Element.from_tag("meta:user-defined") 549 metadata.set_attribute("meta:name", name) 550 self.get_meta_body().append(metadata) 551 metadata.set_attribute("meta:value-type", value_type) 552 metadata.text = value
Inherited Members
2844class NamedRange(Element): 2845 """ODF Named Range "table:named-range". Identifies inside the spreadsheet 2846 a range of cells of a table by a name and the name of the table. 2847 2848 Name Ranges have the following attributes: 2849 2850 name -- name of the named range 2851 2852 table_name -- name of the table 2853 2854 start -- first cell of the named range, tuple (x, y) 2855 2856 end -- last cell of the named range, tuple (x, y) 2857 2858 crange -- range of the named range, tuple (x, y, z, t) 2859 2860 usage -- None or str, usage of the named range. 2861 """ 2862 2863 _tag = "table:named-range" 2864 2865 def __init__( 2866 self, 2867 name: str | None = None, 2868 crange: str | tuple | list | None = None, 2869 table_name: str | None = None, 2870 usage: str | None = None, 2871 **kwargs: Any, 2872 ) -> None: 2873 """Create a Named Range element. 'name' must contains only letters, digits 2874 and '_', and must not be like a coordinate as 'A1'. 'table_name' must be 2875 a correct table name (no "'" or "/" in it). 2876 2877 Arguments: 2878 2879 name -- str, name of the named range 2880 2881 crange -- str or tuple of int, cell or area coordinate 2882 2883 table_name -- str, name of the table 2884 2885 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2886 """ 2887 super().__init__(**kwargs) 2888 self.usage = None 2889 if self._do_init: 2890 self.name = name or "" 2891 self.table_name = _table_name_check(table_name) 2892 self.set_range(crange or "") 2893 self.set_usage(usage) 2894 cell_range_address = self.get_attribute_string("table:cell-range-address") or "" 2895 if not cell_range_address: 2896 self.table_name = "" 2897 self.start = None 2898 self.end = None 2899 self.crange = None 2900 self.usage = None 2901 return 2902 self.usage = self.get_attribute("table:range-usable-as") 2903 name_range = cell_range_address.replace("$", "") 2904 name, crange = name_range.split(".", 1) 2905 if name.startswith("'") and name.endswith("'"): 2906 name = name[1:-1] 2907 self.table_name = name 2908 crange = crange.replace(".", "") 2909 self._set_range(crange) 2910 2911 def set_usage(self, usage: str | None = None) -> None: 2912 """Set the usage of the Named Range. Usage can be None (default) or one 2913 of : 2914 'print-range' 2915 'filter' 2916 'repeat-column' 2917 'repeat-row' 2918 2919 Arguments: 2920 2921 usage -- None or str 2922 """ 2923 if usage is not None: 2924 usage = usage.strip().lower() 2925 if usage not in ("print-range", "filter", "repeat-column", "repeat-row"): 2926 usage = None 2927 if usage is None: 2928 with contextlib.suppress(KeyError): 2929 self.del_attribute("table:range-usable-as") 2930 self.usage = None 2931 else: 2932 self.set_attribute("table:range-usable-as", usage) 2933 self.usage = usage 2934 2935 @property 2936 def name(self) -> str | None: 2937 """Get / set the name of the table.""" 2938 return self.get_attribute_string("table:name") 2939 2940 @name.setter 2941 def name(self, name: str) -> None: 2942 """Set the name of the Named Range. The name is mandatory, if a Named 2943 Range of the same name exists, it will be replaced. Name must contains 2944 only alphanumerics characters and '_', and can not be of a cell 2945 coordinates form like 'AB12'. 2946 2947 Arguments: 2948 2949 name -- str 2950 """ 2951 name = name.strip() 2952 if not name: 2953 raise ValueError("Name required.") 2954 for x in name: 2955 if x in forbidden_in_named_range(): 2956 raise ValueError(f"Character forbidden '{x}' ") 2957 step = "" 2958 for x in name: 2959 if x in string.ascii_letters and step in ("", "A"): 2960 step = "A" 2961 continue 2962 elif step in ("A", "A1") and x in string.digits: 2963 step = "A1" 2964 continue 2965 else: 2966 step = "" 2967 break 2968 if step == "A1": 2969 raise ValueError("Name of the type 'ABC123' is not allowed.") 2970 with contextlib.suppress(Exception): 2971 # we are not on an inserted in a document. 2972 body = self.document_body 2973 named_range = body.get_named_range(name) # type: ignore 2974 if named_range: 2975 named_range.delete() 2976 self.set_attribute("table:name", name) 2977 2978 def set_table_name(self, name: str) -> None: 2979 """Set the name of the table of the Named Range. The name is mandatory. 2980 2981 Arguments: 2982 2983 name -- str 2984 """ 2985 self.table_name = _table_name_check(name) 2986 self._update_attributes() 2987 2988 def _set_range(self, coord: tuple | list | str) -> None: 2989 digits = convert_coordinates(coord) 2990 if len(digits) == 4: 2991 x, y, z, t = digits 2992 else: 2993 x, y = digits 2994 z, t = digits 2995 self.start = x, y # type: ignore 2996 self.end = z, t # type: ignore 2997 self.crange = x, y, z, t # type: ignore 2998 2999 def set_range(self, crange: str | tuple | list) -> None: 3000 """Set the range of the named range. Range can be either one cell 3001 (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric 3002 value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). 3003 3004 Arguments: 3005 3006 crange -- str or tuple of int, cell or area coordinate 3007 """ 3008 self._set_range(crange) 3009 self._update_attributes() 3010 3011 def _update_attributes(self) -> None: 3012 self.set_attribute("table:base-cell-address", self._make_base_cell_address()) 3013 self.set_attribute("table:cell-range-address", self._make_cell_range_address()) 3014 3015 def _make_base_cell_address(self) -> str: 3016 # assuming we got table_name and range 3017 if " " in self.table_name: 3018 name = f"'{self.table_name}'" 3019 else: 3020 name = self.table_name 3021 return f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}" # type: ignore 3022 3023 def _make_cell_range_address(self) -> str: 3024 # assuming we got table_name and range 3025 if " " in self.table_name: 3026 name = f"'{self.table_name}'" 3027 else: 3028 name = self.table_name 3029 if self.start == self.end: 3030 return self._make_base_cell_address() 3031 return ( 3032 f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}:" # type: ignore 3033 f".${digit_to_alpha(self.end[0])}${self.end[1] + 1}" # type: ignore 3034 ) 3035 3036 def get_values( 3037 self, 3038 cell_type: str | None = None, 3039 complete: bool = True, 3040 get_type: bool = False, 3041 flat: bool = False, 3042 ) -> list: 3043 """Shortcut to retrieve the values of the cells of the named range. See 3044 table.get_values() for the arguments description and return format. 3045 """ 3046 body = self.document_body 3047 if not body: 3048 raise ValueError("Table is not inside a document.") 3049 table = body.get_table(name=self.table_name) 3050 if table is None: 3051 raise ValueError 3052 return table.get_values(self.crange, cell_type, complete, get_type, flat) # type: ignore 3053 3054 def get_value(self, get_type: bool = False) -> Any: 3055 """Shortcut to retrieve the value of the first cell of the named range. 3056 See table.get_value() for the arguments description and return format. 3057 """ 3058 body = self.document_body 3059 if not body: 3060 raise ValueError("Table is not inside a document.") 3061 table = body.get_table(name=self.table_name) 3062 if table is None: 3063 raise ValueError 3064 return table.get_value(self.start, get_type) # type: ignore 3065 3066 def set_values( 3067 self, 3068 values: list, 3069 style: str | None = None, 3070 cell_type: str | None = None, 3071 currency: str | None = None, 3072 ) -> None: 3073 """Shortcut to set the values of the cells of the named range. 3074 See table.set_values() for the arguments description. 3075 """ 3076 body = self.document_body 3077 if not body: 3078 raise ValueError("Table is not inside a document.") 3079 table = body.get_table(name=self.table_name) 3080 if table is None: 3081 raise ValueError 3082 table.set_values( # type: ignore 3083 values, 3084 coord=self.crange, 3085 style=style, 3086 cell_type=cell_type, 3087 currency=currency, 3088 ) 3089 3090 def set_value( 3091 self, 3092 value: Any, 3093 cell_type: str | None = None, 3094 currency: str | None = None, 3095 style: str | None = None, 3096 ) -> None: 3097 """Shortcut to set the value of the first cell of the named range. 3098 See table.set_value() for the arguments description. 3099 """ 3100 body = self.document_body 3101 if not body: 3102 raise ValueError("Table is not inside a document.") 3103 table = body.get_table(name=self.table_name) 3104 if table is None: 3105 raise ValueError 3106 table.set_value( # type: ignore 3107 coord=self.start, 3108 value=value, 3109 cell_type=cell_type, 3110 currency=currency, 3111 style=style, 3112 )
ODF Named Range "table:named-range". Identifies inside the spreadsheet a range of cells of a table by a name and the name of the table.
Name Ranges have the following attributes:
name -- name of the named range
table_name -- name of the table
start -- first cell of the named range, tuple (x, y)
end -- last cell of the named range, tuple (x, y)
crange -- range of the named range, tuple (x, y, z, t)
usage -- None or str, usage of the named range.
2865 def __init__( 2866 self, 2867 name: str | None = None, 2868 crange: str | tuple | list | None = None, 2869 table_name: str | None = None, 2870 usage: str | None = None, 2871 **kwargs: Any, 2872 ) -> None: 2873 """Create a Named Range element. 'name' must contains only letters, digits 2874 and '_', and must not be like a coordinate as 'A1'. 'table_name' must be 2875 a correct table name (no "'" or "/" in it). 2876 2877 Arguments: 2878 2879 name -- str, name of the named range 2880 2881 crange -- str or tuple of int, cell or area coordinate 2882 2883 table_name -- str, name of the table 2884 2885 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2886 """ 2887 super().__init__(**kwargs) 2888 self.usage = None 2889 if self._do_init: 2890 self.name = name or "" 2891 self.table_name = _table_name_check(table_name) 2892 self.set_range(crange or "") 2893 self.set_usage(usage) 2894 cell_range_address = self.get_attribute_string("table:cell-range-address") or "" 2895 if not cell_range_address: 2896 self.table_name = "" 2897 self.start = None 2898 self.end = None 2899 self.crange = None 2900 self.usage = None 2901 return 2902 self.usage = self.get_attribute("table:range-usable-as") 2903 name_range = cell_range_address.replace("$", "") 2904 name, crange = name_range.split(".", 1) 2905 if name.startswith("'") and name.endswith("'"): 2906 name = name[1:-1] 2907 self.table_name = name 2908 crange = crange.replace(".", "") 2909 self._set_range(crange)
Create a Named Range element. 'name' must contains only letters, digits and '_', and must not be like a coordinate as 'A1'. 'table_name' must be a correct table name (no "'" or "/" in it).
Arguments:
name -- str, name of the named range
crange -- str or tuple of int, cell or area coordinate
table_name -- str, name of the table
usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2911 def set_usage(self, usage: str | None = None) -> None: 2912 """Set the usage of the Named Range. Usage can be None (default) or one 2913 of : 2914 'print-range' 2915 'filter' 2916 'repeat-column' 2917 'repeat-row' 2918 2919 Arguments: 2920 2921 usage -- None or str 2922 """ 2923 if usage is not None: 2924 usage = usage.strip().lower() 2925 if usage not in ("print-range", "filter", "repeat-column", "repeat-row"): 2926 usage = None 2927 if usage is None: 2928 with contextlib.suppress(KeyError): 2929 self.del_attribute("table:range-usable-as") 2930 self.usage = None 2931 else: 2932 self.set_attribute("table:range-usable-as", usage) 2933 self.usage = usage
Set the usage of the Named Range. Usage can be None (default) or one of : 'print-range' 'filter' 'repeat-column' 'repeat-row'
Arguments:
usage -- None or str
2935 @property 2936 def name(self) -> str | None: 2937 """Get / set the name of the table.""" 2938 return self.get_attribute_string("table:name")
Get / set the name of the table.
2978 def set_table_name(self, name: str) -> None: 2979 """Set the name of the table of the Named Range. The name is mandatory. 2980 2981 Arguments: 2982 2983 name -- str 2984 """ 2985 self.table_name = _table_name_check(name) 2986 self._update_attributes()
Set the name of the table of the Named Range. The name is mandatory.
Arguments:
name -- str
2999 def set_range(self, crange: str | tuple | list) -> None: 3000 """Set the range of the named range. Range can be either one cell 3001 (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric 3002 value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). 3003 3004 Arguments: 3005 3006 crange -- str or tuple of int, cell or area coordinate 3007 """ 3008 self._set_range(crange) 3009 self._update_attributes()
Set the range of the named range. Range can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
Arguments:
crange -- str or tuple of int, cell or area coordinate
3036 def get_values( 3037 self, 3038 cell_type: str | None = None, 3039 complete: bool = True, 3040 get_type: bool = False, 3041 flat: bool = False, 3042 ) -> list: 3043 """Shortcut to retrieve the values of the cells of the named range. See 3044 table.get_values() for the arguments description and return format. 3045 """ 3046 body = self.document_body 3047 if not body: 3048 raise ValueError("Table is not inside a document.") 3049 table = body.get_table(name=self.table_name) 3050 if table is None: 3051 raise ValueError 3052 return table.get_values(self.crange, cell_type, complete, get_type, flat) # type: ignore
Shortcut to retrieve the values of the cells of the named range. See table.get_values() for the arguments description and return format.
3054 def get_value(self, get_type: bool = False) -> Any: 3055 """Shortcut to retrieve the value of the first cell of the named range. 3056 See table.get_value() for the arguments description and return format. 3057 """ 3058 body = self.document_body 3059 if not body: 3060 raise ValueError("Table is not inside a document.") 3061 table = body.get_table(name=self.table_name) 3062 if table is None: 3063 raise ValueError 3064 return table.get_value(self.start, get_type) # type: ignore
Shortcut to retrieve the value of the first cell of the named range. See table.get_value() for the arguments description and return format.
3066 def set_values( 3067 self, 3068 values: list, 3069 style: str | None = None, 3070 cell_type: str | None = None, 3071 currency: str | None = None, 3072 ) -> None: 3073 """Shortcut to set the values of the cells of the named range. 3074 See table.set_values() for the arguments description. 3075 """ 3076 body = self.document_body 3077 if not body: 3078 raise ValueError("Table is not inside a document.") 3079 table = body.get_table(name=self.table_name) 3080 if table is None: 3081 raise ValueError 3082 table.set_values( # type: ignore 3083 values, 3084 coord=self.crange, 3085 style=style, 3086 cell_type=cell_type, 3087 currency=currency, 3088 )
Shortcut to set the values of the cells of the named range. See table.set_values() for the arguments description.
3090 def set_value( 3091 self, 3092 value: Any, 3093 cell_type: str | None = None, 3094 currency: str | None = None, 3095 style: str | None = None, 3096 ) -> None: 3097 """Shortcut to set the value of the first cell of the named range. 3098 See table.set_value() for the arguments description. 3099 """ 3100 body = self.document_body 3101 if not body: 3102 raise ValueError("Table is not inside a document.") 3103 table = body.get_table(name=self.table_name) 3104 if table is None: 3105 raise ValueError 3106 table.set_value( # type: ignore 3107 coord=self.start, 3108 value=value, 3109 cell_type=cell_type, 3110 currency=currency, 3111 style=style, 3112 )
Shortcut to set the value of the first cell of the named range. See table.set_value() for the arguments description.
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
57class Note(Element): 58 """Either a footnote or a endnote element with the given text, 59 optionally referencing it using the given note_id. 60 61 Arguments: 62 63 note_class -- 'footnote' or 'endnote' 64 65 note_id -- str 66 67 citation -- str 68 69 body -- str or Element 70 """ 71 72 _tag = "text:note" 73 _properties = ( 74 PropDef("note_class", "text:note-class"), 75 PropDef("note_id", "text:id"), 76 ) 77 78 def __init__( 79 self, 80 note_class: str = "footnote", 81 note_id: str | None = None, 82 citation: str | None = None, 83 body: str | None = None, 84 **kwargs: Any, 85 ) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.insert(Element.from_tag("text:note-body"), position=0) 89 self.insert(Element.from_tag("text:note-citation"), position=0) 90 self.note_class = note_class 91 if note_id is not None: 92 self.note_id = note_id 93 if citation is not None: 94 self.citation = citation 95 if body is not None: 96 self.note_body = body 97 98 @property 99 def citation(self) -> str: 100 note_citation = self.get_element("text:note-citation") 101 if note_citation: 102 return note_citation.text 103 return "" 104 105 @citation.setter 106 def citation(self, text: str | None) -> None: 107 note_citation = self.get_element("text:note-citation") 108 if note_citation: 109 note_citation.text = text # type:ignore 110 111 @property 112 def note_body(self) -> str: 113 note_body = self.get_element("text:note-body") 114 if note_body: 115 return note_body.text_content 116 return "" 117 118 @note_body.setter 119 def note_body(self, text_or_element: Element | str | None) -> None: 120 note_body = self.get_element("text:note-body") 121 if not note_body: 122 return None 123 if text_or_element is None: 124 note_body.text_content = "" 125 elif isinstance(text_or_element, str): 126 note_body.text_content = text_or_element 127 elif isinstance(text_or_element, Element): 128 note_body.clear() 129 note_body.append(text_or_element) 130 else: 131 raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"') 132 133 def check_validity(self) -> None: 134 if not self.note_class: 135 raise ValueError('Note class must be "footnote" or "endnote"') 136 if not self.note_id: 137 raise ValueError("Note must have an id") 138 if not self.citation: 139 raise ValueError("Note must have a citation") 140 if not self.note_body: 141 pass
Either a footnote or a endnote element with the given text, optionally referencing it using the given note_id.
Arguments:
note_class -- 'footnote' or 'endnote'
note_id -- str
citation -- str
body -- str or Element
78 def __init__( 79 self, 80 note_class: str = "footnote", 81 note_id: str | None = None, 82 citation: str | None = None, 83 body: str | None = None, 84 **kwargs: Any, 85 ) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.insert(Element.from_tag("text:note-body"), position=0) 89 self.insert(Element.from_tag("text:note-citation"), position=0) 90 self.note_class = note_class 91 if note_id is not None: 92 self.note_id = note_id 93 if citation is not None: 94 self.citation = citation 95 if body is not None: 96 self.note_body = body
133 def check_validity(self) -> None: 134 if not self.note_class: 135 raise ValueError('Note class must be "footnote" or "endnote"') 136 if not self.note_id: 137 raise ValueError("Note must have an id") 138 if not self.citation: 139 raise ValueError("Note must have a citation") 140 if not self.note_body: 141 pass
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
147class Paragraph(ParagraphBase): 148 """Specialised element for paragraphs "text:p". The "text:p" element 149 represents a paragraph, which is the basic unit of text in an OpenDocument 150 file. 151 """ 152 153 _tag = "text:p" 154 155 def __init__( 156 self, 157 text_or_element: str | Element | None = None, 158 style: str | None = None, 159 **kwargs: Any, 160 ): 161 """Create a paragraph element of the given style containing the optional 162 given text. 163 164 Arguments: 165 166 text -- str or Element 167 168 style -- str 169 """ 170 super().__init__(**kwargs) 171 if self._do_init: 172 if isinstance(text_or_element, Element): 173 self.append(text_or_element) 174 else: 175 self.text = text_or_element # type:ignore 176 if style is not None: 177 self.style = style 178 179 def insert_note( 180 self, 181 note_element: Note | None = None, 182 after: str | Element | None = None, 183 note_class: str = "footnote", 184 note_id: str | None = None, 185 citation: str | None = None, 186 body: str | None = None, 187 ) -> None: 188 if note_element is None: 189 note_element = Note( 190 note_class=note_class, note_id=note_id, citation=citation, body=body 191 ) 192 else: 193 # XXX clone or modify the argument? 194 if note_class: 195 note_element.note_class = note_class 196 if note_id: 197 note_element.note_id = note_id 198 if citation: 199 note_element.citation = citation 200 if body: 201 note_element.note_body = body 202 note_element.check_validity() 203 if isinstance(after, str): 204 self._insert(note_element, after=after, main_text=True) 205 elif isinstance(after, Element): 206 after.insert(note_element, FIRST_CHILD) 207 else: 208 self.insert(note_element, FIRST_CHILD) 209 210 def insert_annotation( # noqa: C901 211 self, 212 annotation_element: Annotation | None = None, 213 before: str | None = None, 214 after: str | Element | None = None, 215 position: int | tuple = 0, 216 content: str | Element | None = None, 217 body: str | None = None, 218 creator: str | None = None, 219 date: datetime | None = None, 220 ) -> Annotation: 221 """Insert an annotation, at the position defined by the regex (before, 222 after, content) or by positionnal argument (position). If content is 223 provided, the annotation covers the full content regex. Else, the 224 annotation is positionned either 'before' or 'after' provided regex. 225 226 If content is an odf element (ie: paragraph, span, ...), the full inner 227 content is covered by the annotation (of the position just after if 228 content is a single empty tag). 229 230 If content/before or after exists (regex) and return a group of matching 231 positions, the position value is the index of matching place to use. 232 233 annotation_element can contain a previously created annotation, else 234 the annotation is created from the body, creator and optional date 235 (current date by default). 236 237 Arguments: 238 239 annotation_element -- Annotation or None 240 241 before -- str regular expression or None 242 243 after -- str regular expression or Element or None 244 245 content -- str regular expression or None, or Element 246 247 position -- int or tuple of int 248 249 body -- str or Element 250 251 creator -- str 252 253 date -- datetime 254 """ 255 256 if annotation_element is None: 257 annotation_element = Annotation( 258 text_or_element=body, creator=creator, date=date, parent=self 259 ) 260 else: 261 # XXX clone or modify the argument? 262 if body: 263 annotation_element.note_body = body 264 if creator: 265 annotation_element.dc_creator = creator 266 if date: 267 annotation_element.dc_date = date 268 annotation_element.check_validity() 269 270 # special case: content is an odf element (ie: a paragraph) 271 if isinstance(content, Element): 272 if content.is_empty(): 273 content.insert(annotation_element, xmlposition=NEXT_SIBLING) 274 return annotation_element 275 content.insert(annotation_element, start=True) 276 annotation_end = AnnotationEnd(annotation_element) 277 content.append(annotation_end) 278 return annotation_element 279 280 # special case 281 if isinstance(after, Element): 282 after.insert(annotation_element, FIRST_CHILD) 283 return annotation_element 284 285 # With "content" => automatically insert a "start" and an "end" 286 # bookmark 287 if ( 288 before is None 289 and after is None 290 and content is not None 291 and isinstance(position, int) 292 ): 293 # Start tag 294 self._insert( 295 annotation_element, before=content, position=position, main_text=True 296 ) 297 # End tag 298 annotation_end = AnnotationEnd(annotation_element) 299 self._insert( 300 annotation_end, after=content, position=position, main_text=True 301 ) 302 return annotation_element 303 304 # With "(int, int)" => automatically insert a "start" and an "end" 305 # bookmark 306 if ( 307 before is None 308 and after is None 309 and content is None 310 and isinstance(position, tuple) 311 ): 312 # Start 313 self._insert(annotation_element, position=position[0], main_text=True) 314 # End 315 annotation_end = AnnotationEnd(annotation_element) 316 self._insert(annotation_end, position=position[1], main_text=True) 317 return annotation_element 318 319 # Without "content" nor "position" 320 if content is not None or not isinstance(position, int): 321 raise ValueError("Bad arguments") 322 323 # Insert 324 self._insert( 325 annotation_element, 326 before=before, 327 after=after, 328 position=position, 329 main_text=True, 330 ) 331 return annotation_element 332 333 def insert_annotation_end( 334 self, 335 annotation_element: Annotation, 336 before: str | None = None, 337 after: str | None = None, 338 position: int = 0, 339 ) -> AnnotationEnd: 340 """Insert an annotation end tag for an existing annotation. If some end 341 tag already exists, replace it. Annotation end tag is set at the 342 position defined by the regex (before or after). 343 344 If content/before or after (regex) returns a group of matching 345 positions, the position value is the index of matching place to use. 346 347 Arguments: 348 349 annotation_element -- Annotation (mandatory) 350 351 before -- str regular expression or None 352 353 after -- str regular expression or None 354 355 position -- int 356 """ 357 358 if annotation_element is None: 359 raise ValueError 360 if not isinstance(annotation_element, Annotation): 361 raise TypeError("Not a <office:annotation> Annotation") 362 363 # remove existing end tag 364 name = annotation_element.name 365 existing_end_tag = self.get_annotation_end(name=name) 366 if existing_end_tag: 367 existing_end_tag.delete() 368 369 # create the end tag 370 end_tag = AnnotationEnd(annotation_element) 371 372 # Insert 373 self._insert( 374 end_tag, before=before, after=after, position=position, main_text=True 375 ) 376 return end_tag 377 378 def set_reference_mark( 379 self, 380 name: str, 381 before: str | None = None, 382 after: str | None = None, 383 position: int = 0, 384 content: str | Element | None = None, 385 ) -> Element: 386 """Insert a reference mark, at the position defined by the regex 387 (before, after, content) or by positionnal argument (position). If 388 content is provided, the annotation covers the full range content regex 389 (instances of ReferenceMarkStart and ReferenceMarkEnd are 390 created). Else, an instance of ReferenceMark is positionned either 391 'before' or 'after' provided regex. 392 393 If content is an ODF Element (ie: Paragraph, Span, ...), the full inner 394 content is referenced (of the position just after if content is a single 395 empty tag). 396 397 If content/before or after exists (regex) and return a group of matching 398 positions, the position value is the index of matching place to use. 399 400 Name is mandatory and shall be unique in the document for the preference 401 mark range. 402 403 Arguments: 404 405 name -- str 406 407 before -- str regular expression or None 408 409 after -- str regular expression or None, 410 411 content -- str regular expression or None, or Element 412 413 position -- int or tuple of int 414 415 Return: the created ReferenceMark or ReferenceMarkStart 416 """ 417 # special case: content is an odf element (ie: a paragraph) 418 if isinstance(content, Element): 419 if content.is_empty(): 420 reference = ReferenceMark(name) 421 content.insert(reference, xmlposition=NEXT_SIBLING) 422 return reference 423 reference_start = ReferenceMarkStart(name) 424 content.insert(reference_start, start=True) 425 reference_end = ReferenceMarkEnd(name) 426 content.append(reference_end) 427 return reference_start 428 429 # With "content" => automatically insert a "start" and an "end" 430 # reference 431 if ( 432 before is None 433 and after is None 434 and content is not None 435 and isinstance(position, int) 436 ): 437 # Start tag 438 reference_start = ReferenceMarkStart(name) 439 self._insert( 440 reference_start, before=content, position=position, main_text=True 441 ) 442 # End tag 443 reference_end = ReferenceMarkEnd(name) 444 self._insert( 445 reference_end, after=content, position=position, main_text=True 446 ) 447 return reference_start 448 449 # With "(int, int)" => automatically insert a "start" and an "end" 450 if ( 451 before is None 452 and after is None 453 and content is None 454 and isinstance(position, tuple) 455 ): 456 # Start 457 reference_start = ReferenceMarkStart(name) 458 self._insert(reference_start, position=position[0], main_text=True) 459 # End 460 reference_end = ReferenceMarkEnd(name) 461 self._insert(reference_end, position=position[1], main_text=True) 462 return reference_start 463 464 # Without "content" nor "position" 465 if content is not None or not isinstance(position, int): 466 raise ValueError("bad arguments") 467 468 # Insert a positional reference mark 469 reference = ReferenceMark(name) 470 self._insert( 471 reference, 472 before=before, 473 after=after, 474 position=position, 475 main_text=True, 476 ) 477 return reference 478 479 def set_reference_mark_end( 480 self, 481 reference_mark: Element, 482 before: str | None = None, 483 after: str | None = None, 484 position: int = 0, 485 ) -> ReferenceMarkEnd: 486 """Insert/move a ReferenceMarkEnd for an existing reference mark. If 487 some end tag already exists, replace it. Reference tag is set at the 488 position defined by the regex (before or after). 489 490 If content/before or after (regex) returns a group of matching 491 positions, the position value is the index of matching place to use. 492 493 Arguments: 494 495 reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory) 496 497 before -- str regular expression or None 498 499 after -- str regular expression or None 500 501 position -- int 502 """ 503 if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)): 504 raise TypeError("Not a ReferenceMark or ReferenceMarkStart") 505 name = reference_mark.name 506 if isinstance(reference_mark, ReferenceMark): 507 # change it to a range reference: 508 reference_mark.tag = ReferenceMarkStart._tag 509 510 existing_end_tag = self.get_reference_mark_end(name=name) 511 if existing_end_tag: 512 existing_end_tag.delete() 513 514 # create the end tag 515 end_tag = ReferenceMarkEnd(name) 516 517 # Insert 518 self._insert( 519 end_tag, before=before, after=after, position=position, main_text=True 520 ) 521 return end_tag 522 523 def insert_variable(self, variable_element: Element, after: str | None) -> None: 524 self._insert(variable_element, after=after, main_text=True) 525 526 @_by_regex_offset 527 def set_span( 528 self, 529 match: str, 530 tail: str, 531 style: str, 532 regex: str | None = None, 533 offset: int | None = None, 534 length: int = 0, 535 ) -> Span: 536 """ 537 set_span(style, regex=None, offset=None, length=0) 538 Apply the given style to text content matching the regex OR the 539 positional arguments offset and length. 540 541 (match, tail: provided by regex decorator) 542 543 Arguments: 544 545 style -- str 546 547 regex -- str regular expression 548 549 offset -- int 550 551 length -- int 552 """ 553 span = Span(match, style=style) 554 span.tail = tail 555 return span 556 557 def remove_spans(self, keep_heading: bool = True) -> Element | list: 558 """Send back a copy of the element, without span styles. 559 If keep_heading is True (default), the first level heading style is left 560 unchanged. 561 """ 562 strip = (Span._tag,) 563 if keep_heading: 564 protect = ("text:h",) 565 else: 566 protect = None 567 return self.strip_tags(strip=strip, protect=protect) 568 569 def remove_span(self, spans: Element | list[Element]) -> Element | list: 570 """Send back a copy of the element, the spans (not a clone) removed. 571 572 Arguments: 573 574 spans -- Element or list of Element 575 """ 576 return self.strip_elements(spans) 577 578 @_by_regex_offset 579 def set_link( 580 self, 581 match: str, 582 tail: str, 583 url: str, 584 regex: str | None = None, 585 offset: int | None = None, 586 length: int = 0, 587 ) -> Element: 588 """ 589 set_link(url, regex=None, offset=None, length=0) 590 Make a link to the provided url from text content matching the regex 591 OR the positional arguments offset and length. 592 593 (match, tail: provided by regex decorator) 594 595 Arguments: 596 597 url -- str 598 599 regex -- str regular expression 600 601 offset -- int 602 603 length -- int 604 """ 605 link = Link(url, text=match) 606 link.tail = tail 607 return link 608 609 def remove_links(self) -> Element | list: 610 """Send back a copy of the element, without links tags.""" 611 strip = (Link._tag,) 612 return self.strip_tags(strip=strip) 613 614 def remove_link(self, links: Link | list[Link]) -> Element | list: 615 """Send back a copy of the element (not a clone), with the sub links 616 removed. 617 618 Arguments: 619 620 links -- Link or list of Link 621 """ 622 return self.strip_elements(links) 623 624 def insert_reference( 625 self, 626 name: str, 627 ref_format: str = "", 628 before: str | None = None, 629 after: str | Element | None = None, 630 position: int = 0, 631 display: str | None = None, 632 ) -> None: 633 """Create and insert a reference to a content marked by a reference 634 mark. The Reference element ("text:reference-ref") represents a 635 field that references a "text:reference-mark-start" or 636 "text:reference-mark" element. Its "text:reference-format" attribute 637 specifies what is displayed from the referenced element. Default is 638 'page'. Actual content is not automatically updated except for the 'text' 639 format. 640 641 name is mandatory and should represent an existing reference mark of the 642 document. 643 644 ref_format is the argument for format reference (default is 'page'). 645 646 The reference is inserted the position defined by the regex (before / 647 after), or by positionnal argument (position). If 'display' is provided, 648 it will be used as the text value for the reference. 649 650 If after is an ODF Element, the reference is inserted as first child of 651 this element. 652 653 Arguments: 654 655 name -- str 656 657 ref_format -- one of : 'chapter', 'direction', 'page', 'text', 658 'caption', 'category-and-value', 'value', 659 'number', 'number-all-superior', 660 'number-no-superior' 661 662 before -- str regular expression or None 663 664 after -- str regular expression or odf element or None 665 666 position -- int 667 668 display -- str or None 669 """ 670 reference = Reference(name, ref_format) 671 if display is None and ref_format == "text": 672 # get reference content 673 body = self.document_body 674 if not body: 675 body = self.root 676 mark = body.get_reference_mark(name=name) 677 if mark: 678 display = mark.referenced_text # type: ignore 679 if not display: 680 display = " " 681 reference.text = display 682 if isinstance(after, Element): 683 after.insert(reference, FIRST_CHILD) 684 else: 685 self._insert( 686 reference, before=before, after=after, position=position, main_text=True 687 ) 688 689 def set_bookmark( 690 self, 691 name: str, 692 before: str | None = None, 693 after: str | None = None, 694 position: int | tuple = 0, 695 role: str | None = None, 696 content: str | None = None, 697 ) -> Element | tuple[Element, Element]: 698 """Insert a bookmark before or after the characters in the text which 699 match the regex before/after. When the regex matches more of one part 700 of the text, position can be set to choose which part must be used. 701 If before and after are None, we use only position that is the number 702 of characters. 703 704 So, by default, this function inserts a bookmark before the first 705 character of the content. Role can be None, "start" or "end", we 706 insert respectively a position bookmark a bookmark-start or a 707 bookmark-end. 708 709 If content is not None these 2 calls are equivalent: 710 711 paragraph.set_bookmark("bookmark", content="xyz") 712 713 and: 714 715 paragraph.set_bookmark("bookmark", before="xyz", role="start") 716 paragraph.set_bookmark("bookmark", after="xyz", role="end") 717 718 719 If position is a 2-tuple, these 2 calls are equivalent: 720 721 paragraph.set_bookmark("bookmark", position=(10, 20)) 722 723 and: 724 725 paragraph.set_bookmark("bookmark", position=10, role="start") 726 paragraph.set_bookmark("bookmark", position=20, role="end") 727 728 729 Arguments: 730 731 name -- str 732 733 before -- str regex 734 735 after -- str regex 736 737 position -- int or (int, int) 738 739 role -- None, "start" or "end" 740 741 content -- str regex 742 """ 743 # With "content" => automatically insert a "start" and an "end" 744 # bookmark 745 if ( 746 before is None 747 and after is None 748 and role is None 749 and content is not None 750 and isinstance(position, int) 751 ): 752 # Start 753 start = BookmarkStart(name) 754 self._insert(start, before=content, position=position, main_text=True) 755 # End 756 end = BookmarkEnd(name) 757 self._insert(end, after=content, position=position, main_text=True) 758 return start, end 759 760 # With "(int, int)" => automatically insert a "start" and an "end" 761 # bookmark 762 if ( 763 before is None 764 and after is None 765 and role is None 766 and content is None 767 and isinstance(position, tuple) 768 ): 769 # Start 770 start = BookmarkStart(name) 771 self._insert(start, position=position[0], main_text=True) 772 # End 773 end = BookmarkEnd(name) 774 self._insert(end, position=position[1], main_text=True) 775 return start, end 776 777 # Without "content" nor "position" 778 if content is not None or not isinstance(position, int): 779 raise ValueError("bad arguments") 780 781 # Role 782 if role is None: 783 bookmark: Element = Bookmark(name) 784 elif role == "start": 785 bookmark = BookmarkStart(name) 786 elif role == "end": 787 bookmark = BookmarkEnd(name) 788 else: 789 raise ValueError("bad arguments") 790 791 # Insert 792 self._insert( 793 bookmark, before=before, after=after, position=position, main_text=True 794 ) 795 796 return bookmark
Specialised element for paragraphs "text:p". The "text:p" element represents a paragraph, which is the basic unit of text in an OpenDocument file.
155 def __init__( 156 self, 157 text_or_element: str | Element | None = None, 158 style: str | None = None, 159 **kwargs: Any, 160 ): 161 """Create a paragraph element of the given style containing the optional 162 given text. 163 164 Arguments: 165 166 text -- str or Element 167 168 style -- str 169 """ 170 super().__init__(**kwargs) 171 if self._do_init: 172 if isinstance(text_or_element, Element): 173 self.append(text_or_element) 174 else: 175 self.text = text_or_element # type:ignore 176 if style is not None: 177 self.style = style
Create a paragraph element of the given style containing the optional given text.
Arguments:
text -- str or Element
style -- str
179 def insert_note( 180 self, 181 note_element: Note | None = None, 182 after: str | Element | None = None, 183 note_class: str = "footnote", 184 note_id: str | None = None, 185 citation: str | None = None, 186 body: str | None = None, 187 ) -> None: 188 if note_element is None: 189 note_element = Note( 190 note_class=note_class, note_id=note_id, citation=citation, body=body 191 ) 192 else: 193 # XXX clone or modify the argument? 194 if note_class: 195 note_element.note_class = note_class 196 if note_id: 197 note_element.note_id = note_id 198 if citation: 199 note_element.citation = citation 200 if body: 201 note_element.note_body = body 202 note_element.check_validity() 203 if isinstance(after, str): 204 self._insert(note_element, after=after, main_text=True) 205 elif isinstance(after, Element): 206 after.insert(note_element, FIRST_CHILD) 207 else: 208 self.insert(note_element, FIRST_CHILD)
210 def insert_annotation( # noqa: C901 211 self, 212 annotation_element: Annotation | None = None, 213 before: str | None = None, 214 after: str | Element | None = None, 215 position: int | tuple = 0, 216 content: str | Element | None = None, 217 body: str | None = None, 218 creator: str | None = None, 219 date: datetime | None = None, 220 ) -> Annotation: 221 """Insert an annotation, at the position defined by the regex (before, 222 after, content) or by positionnal argument (position). If content is 223 provided, the annotation covers the full content regex. Else, the 224 annotation is positionned either 'before' or 'after' provided regex. 225 226 If content is an odf element (ie: paragraph, span, ...), the full inner 227 content is covered by the annotation (of the position just after if 228 content is a single empty tag). 229 230 If content/before or after exists (regex) and return a group of matching 231 positions, the position value is the index of matching place to use. 232 233 annotation_element can contain a previously created annotation, else 234 the annotation is created from the body, creator and optional date 235 (current date by default). 236 237 Arguments: 238 239 annotation_element -- Annotation or None 240 241 before -- str regular expression or None 242 243 after -- str regular expression or Element or None 244 245 content -- str regular expression or None, or Element 246 247 position -- int or tuple of int 248 249 body -- str or Element 250 251 creator -- str 252 253 date -- datetime 254 """ 255 256 if annotation_element is None: 257 annotation_element = Annotation( 258 text_or_element=body, creator=creator, date=date, parent=self 259 ) 260 else: 261 # XXX clone or modify the argument? 262 if body: 263 annotation_element.note_body = body 264 if creator: 265 annotation_element.dc_creator = creator 266 if date: 267 annotation_element.dc_date = date 268 annotation_element.check_validity() 269 270 # special case: content is an odf element (ie: a paragraph) 271 if isinstance(content, Element): 272 if content.is_empty(): 273 content.insert(annotation_element, xmlposition=NEXT_SIBLING) 274 return annotation_element 275 content.insert(annotation_element, start=True) 276 annotation_end = AnnotationEnd(annotation_element) 277 content.append(annotation_end) 278 return annotation_element 279 280 # special case 281 if isinstance(after, Element): 282 after.insert(annotation_element, FIRST_CHILD) 283 return annotation_element 284 285 # With "content" => automatically insert a "start" and an "end" 286 # bookmark 287 if ( 288 before is None 289 and after is None 290 and content is not None 291 and isinstance(position, int) 292 ): 293 # Start tag 294 self._insert( 295 annotation_element, before=content, position=position, main_text=True 296 ) 297 # End tag 298 annotation_end = AnnotationEnd(annotation_element) 299 self._insert( 300 annotation_end, after=content, position=position, main_text=True 301 ) 302 return annotation_element 303 304 # With "(int, int)" => automatically insert a "start" and an "end" 305 # bookmark 306 if ( 307 before is None 308 and after is None 309 and content is None 310 and isinstance(position, tuple) 311 ): 312 # Start 313 self._insert(annotation_element, position=position[0], main_text=True) 314 # End 315 annotation_end = AnnotationEnd(annotation_element) 316 self._insert(annotation_end, position=position[1], main_text=True) 317 return annotation_element 318 319 # Without "content" nor "position" 320 if content is not None or not isinstance(position, int): 321 raise ValueError("Bad arguments") 322 323 # Insert 324 self._insert( 325 annotation_element, 326 before=before, 327 after=after, 328 position=position, 329 main_text=True, 330 ) 331 return annotation_element
Insert an annotation, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full content regex. Else, the annotation is positionned either 'before' or 'after' provided regex.
If content is an odf element (ie: paragraph, span, ...), the full inner content is covered by the annotation (of the position just after if content is a single empty tag).
If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.
annotation_element can contain a previously created annotation, else the annotation is created from the body, creator and optional date (current date by default).
Arguments:
annotation_element -- Annotation or None
before -- str regular expression or None
after -- str regular expression or Element or None
content -- str regular expression or None, or Element
position -- int or tuple of int
body -- str or Element
creator -- str
date -- datetime
333 def insert_annotation_end( 334 self, 335 annotation_element: Annotation, 336 before: str | None = None, 337 after: str | None = None, 338 position: int = 0, 339 ) -> AnnotationEnd: 340 """Insert an annotation end tag for an existing annotation. If some end 341 tag already exists, replace it. Annotation end tag is set at the 342 position defined by the regex (before or after). 343 344 If content/before or after (regex) returns a group of matching 345 positions, the position value is the index of matching place to use. 346 347 Arguments: 348 349 annotation_element -- Annotation (mandatory) 350 351 before -- str regular expression or None 352 353 after -- str regular expression or None 354 355 position -- int 356 """ 357 358 if annotation_element is None: 359 raise ValueError 360 if not isinstance(annotation_element, Annotation): 361 raise TypeError("Not a <office:annotation> Annotation") 362 363 # remove existing end tag 364 name = annotation_element.name 365 existing_end_tag = self.get_annotation_end(name=name) 366 if existing_end_tag: 367 existing_end_tag.delete() 368 369 # create the end tag 370 end_tag = AnnotationEnd(annotation_element) 371 372 # Insert 373 self._insert( 374 end_tag, before=before, after=after, position=position, main_text=True 375 ) 376 return end_tag
Insert an annotation end tag for an existing annotation. If some end tag already exists, replace it. Annotation end tag is set at the position defined by the regex (before or after).
If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.
Arguments:
annotation_element -- Annotation (mandatory)
before -- str regular expression or None
after -- str regular expression or None
position -- int
378 def set_reference_mark( 379 self, 380 name: str, 381 before: str | None = None, 382 after: str | None = None, 383 position: int = 0, 384 content: str | Element | None = None, 385 ) -> Element: 386 """Insert a reference mark, at the position defined by the regex 387 (before, after, content) or by positionnal argument (position). If 388 content is provided, the annotation covers the full range content regex 389 (instances of ReferenceMarkStart and ReferenceMarkEnd are 390 created). Else, an instance of ReferenceMark is positionned either 391 'before' or 'after' provided regex. 392 393 If content is an ODF Element (ie: Paragraph, Span, ...), the full inner 394 content is referenced (of the position just after if content is a single 395 empty tag). 396 397 If content/before or after exists (regex) and return a group of matching 398 positions, the position value is the index of matching place to use. 399 400 Name is mandatory and shall be unique in the document for the preference 401 mark range. 402 403 Arguments: 404 405 name -- str 406 407 before -- str regular expression or None 408 409 after -- str regular expression or None, 410 411 content -- str regular expression or None, or Element 412 413 position -- int or tuple of int 414 415 Return: the created ReferenceMark or ReferenceMarkStart 416 """ 417 # special case: content is an odf element (ie: a paragraph) 418 if isinstance(content, Element): 419 if content.is_empty(): 420 reference = ReferenceMark(name) 421 content.insert(reference, xmlposition=NEXT_SIBLING) 422 return reference 423 reference_start = ReferenceMarkStart(name) 424 content.insert(reference_start, start=True) 425 reference_end = ReferenceMarkEnd(name) 426 content.append(reference_end) 427 return reference_start 428 429 # With "content" => automatically insert a "start" and an "end" 430 # reference 431 if ( 432 before is None 433 and after is None 434 and content is not None 435 and isinstance(position, int) 436 ): 437 # Start tag 438 reference_start = ReferenceMarkStart(name) 439 self._insert( 440 reference_start, before=content, position=position, main_text=True 441 ) 442 # End tag 443 reference_end = ReferenceMarkEnd(name) 444 self._insert( 445 reference_end, after=content, position=position, main_text=True 446 ) 447 return reference_start 448 449 # With "(int, int)" => automatically insert a "start" and an "end" 450 if ( 451 before is None 452 and after is None 453 and content is None 454 and isinstance(position, tuple) 455 ): 456 # Start 457 reference_start = ReferenceMarkStart(name) 458 self._insert(reference_start, position=position[0], main_text=True) 459 # End 460 reference_end = ReferenceMarkEnd(name) 461 self._insert(reference_end, position=position[1], main_text=True) 462 return reference_start 463 464 # Without "content" nor "position" 465 if content is not None or not isinstance(position, int): 466 raise ValueError("bad arguments") 467 468 # Insert a positional reference mark 469 reference = ReferenceMark(name) 470 self._insert( 471 reference, 472 before=before, 473 after=after, 474 position=position, 475 main_text=True, 476 ) 477 return reference
Insert a reference mark, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full range content regex (instances of ReferenceMarkStart and ReferenceMarkEnd are created). Else, an instance of ReferenceMark is positionned either 'before' or 'after' provided regex.
If content is an ODF Element (ie: Paragraph, Span, ...), the full inner content is referenced (of the position just after if content is a single empty tag).
If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.
Name is mandatory and shall be unique in the document for the preference mark range.
Arguments:
name -- str
before -- str regular expression or None
after -- str regular expression or None,
content -- str regular expression or None, or Element
position -- int or tuple of int
Return: the created ReferenceMark or ReferenceMarkStart
479 def set_reference_mark_end( 480 self, 481 reference_mark: Element, 482 before: str | None = None, 483 after: str | None = None, 484 position: int = 0, 485 ) -> ReferenceMarkEnd: 486 """Insert/move a ReferenceMarkEnd for an existing reference mark. If 487 some end tag already exists, replace it. Reference tag is set at the 488 position defined by the regex (before or after). 489 490 If content/before or after (regex) returns a group of matching 491 positions, the position value is the index of matching place to use. 492 493 Arguments: 494 495 reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory) 496 497 before -- str regular expression or None 498 499 after -- str regular expression or None 500 501 position -- int 502 """ 503 if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)): 504 raise TypeError("Not a ReferenceMark or ReferenceMarkStart") 505 name = reference_mark.name 506 if isinstance(reference_mark, ReferenceMark): 507 # change it to a range reference: 508 reference_mark.tag = ReferenceMarkStart._tag 509 510 existing_end_tag = self.get_reference_mark_end(name=name) 511 if existing_end_tag: 512 existing_end_tag.delete() 513 514 # create the end tag 515 end_tag = ReferenceMarkEnd(name) 516 517 # Insert 518 self._insert( 519 end_tag, before=before, after=after, position=position, main_text=True 520 ) 521 return end_tag
Insert/move a ReferenceMarkEnd for an existing reference mark. If some end tag already exists, replace it. Reference tag is set at the position defined by the regex (before or after).
If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.
Arguments:
reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
before -- str regular expression or None
after -- str regular expression or None
position -- int
526 @_by_regex_offset 527 def set_span( 528 self, 529 match: str, 530 tail: str, 531 style: str, 532 regex: str | None = None, 533 offset: int | None = None, 534 length: int = 0, 535 ) -> Span: 536 """ 537 set_span(style, regex=None, offset=None, length=0) 538 Apply the given style to text content matching the regex OR the 539 positional arguments offset and length. 540 541 (match, tail: provided by regex decorator) 542 543 Arguments: 544 545 style -- str 546 547 regex -- str regular expression 548 549 offset -- int 550 551 length -- int 552 """ 553 span = Span(match, style=style) 554 span.tail = tail 555 return span
set_span(style, regex=None, offset=None, length=0) Apply the given style to text content matching the regex OR the positional arguments offset and length.
(match, tail: provided by regex decorator)
Arguments:
style -- str
regex -- str regular expression
offset -- int
length -- int
557 def remove_spans(self, keep_heading: bool = True) -> Element | list: 558 """Send back a copy of the element, without span styles. 559 If keep_heading is True (default), the first level heading style is left 560 unchanged. 561 """ 562 strip = (Span._tag,) 563 if keep_heading: 564 protect = ("text:h",) 565 else: 566 protect = None 567 return self.strip_tags(strip=strip, protect=protect)
Send back a copy of the element, without span styles. If keep_heading is True (default), the first level heading style is left unchanged.
569 def remove_span(self, spans: Element | list[Element]) -> Element | list: 570 """Send back a copy of the element, the spans (not a clone) removed. 571 572 Arguments: 573 574 spans -- Element or list of Element 575 """ 576 return self.strip_elements(spans)
Send back a copy of the element, the spans (not a clone) removed.
Arguments:
spans -- Element or list of Element
578 @_by_regex_offset 579 def set_link( 580 self, 581 match: str, 582 tail: str, 583 url: str, 584 regex: str | None = None, 585 offset: int | None = None, 586 length: int = 0, 587 ) -> Element: 588 """ 589 set_link(url, regex=None, offset=None, length=0) 590 Make a link to the provided url from text content matching the regex 591 OR the positional arguments offset and length. 592 593 (match, tail: provided by regex decorator) 594 595 Arguments: 596 597 url -- str 598 599 regex -- str regular expression 600 601 offset -- int 602 603 length -- int 604 """ 605 link = Link(url, text=match) 606 link.tail = tail 607 return link
set_link(url, regex=None, offset=None, length=0) Make a link to the provided url from text content matching the regex OR the positional arguments offset and length.
(match, tail: provided by regex decorator)
Arguments:
url -- str
regex -- str regular expression
offset -- int
length -- int
609 def remove_links(self) -> Element | list: 610 """Send back a copy of the element, without links tags.""" 611 strip = (Link._tag,) 612 return self.strip_tags(strip=strip)
Send back a copy of the element, without links tags.
614 def remove_link(self, links: Link | list[Link]) -> Element | list: 615 """Send back a copy of the element (not a clone), with the sub links 616 removed. 617 618 Arguments: 619 620 links -- Link or list of Link 621 """ 622 return self.strip_elements(links)
Send back a copy of the element (not a clone), with the sub links removed.
Arguments:
links -- Link or list of Link
624 def insert_reference( 625 self, 626 name: str, 627 ref_format: str = "", 628 before: str | None = None, 629 after: str | Element | None = None, 630 position: int = 0, 631 display: str | None = None, 632 ) -> None: 633 """Create and insert a reference to a content marked by a reference 634 mark. The Reference element ("text:reference-ref") represents a 635 field that references a "text:reference-mark-start" or 636 "text:reference-mark" element. Its "text:reference-format" attribute 637 specifies what is displayed from the referenced element. Default is 638 'page'. Actual content is not automatically updated except for the 'text' 639 format. 640 641 name is mandatory and should represent an existing reference mark of the 642 document. 643 644 ref_format is the argument for format reference (default is 'page'). 645 646 The reference is inserted the position defined by the regex (before / 647 after), or by positionnal argument (position). If 'display' is provided, 648 it will be used as the text value for the reference. 649 650 If after is an ODF Element, the reference is inserted as first child of 651 this element. 652 653 Arguments: 654 655 name -- str 656 657 ref_format -- one of : 'chapter', 'direction', 'page', 'text', 658 'caption', 'category-and-value', 'value', 659 'number', 'number-all-superior', 660 'number-no-superior' 661 662 before -- str regular expression or None 663 664 after -- str regular expression or odf element or None 665 666 position -- int 667 668 display -- str or None 669 """ 670 reference = Reference(name, ref_format) 671 if display is None and ref_format == "text": 672 # get reference content 673 body = self.document_body 674 if not body: 675 body = self.root 676 mark = body.get_reference_mark(name=name) 677 if mark: 678 display = mark.referenced_text # type: ignore 679 if not display: 680 display = " " 681 reference.text = display 682 if isinstance(after, Element): 683 after.insert(reference, FIRST_CHILD) 684 else: 685 self._insert( 686 reference, before=before, after=after, position=position, main_text=True 687 )
Create and insert a reference to a content marked by a reference mark. The Reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its "text:reference-format" attribute specifies what is displayed from the referenced element. Default is 'page'. Actual content is not automatically updated except for the 'text' format.
name is mandatory and should represent an existing reference mark of the document.
ref_format is the argument for format reference (default is 'page').
The reference is inserted the position defined by the regex (before / after), or by positionnal argument (position). If 'display' is provided, it will be used as the text value for the reference.
If after is an ODF Element, the reference is inserted as first child of this element.
Arguments:
name -- str
ref_format -- one of : 'chapter', 'direction', 'page', 'text',
'caption', 'category-and-value', 'value',
'number', 'number-all-superior',
'number-no-superior'
before -- str regular expression or None
after -- str regular expression or odf element or None
position -- int
display -- str or None
689 def set_bookmark( 690 self, 691 name: str, 692 before: str | None = None, 693 after: str | None = None, 694 position: int | tuple = 0, 695 role: str | None = None, 696 content: str | None = None, 697 ) -> Element | tuple[Element, Element]: 698 """Insert a bookmark before or after the characters in the text which 699 match the regex before/after. When the regex matches more of one part 700 of the text, position can be set to choose which part must be used. 701 If before and after are None, we use only position that is the number 702 of characters. 703 704 So, by default, this function inserts a bookmark before the first 705 character of the content. Role can be None, "start" or "end", we 706 insert respectively a position bookmark a bookmark-start or a 707 bookmark-end. 708 709 If content is not None these 2 calls are equivalent: 710 711 paragraph.set_bookmark("bookmark", content="xyz") 712 713 and: 714 715 paragraph.set_bookmark("bookmark", before="xyz", role="start") 716 paragraph.set_bookmark("bookmark", after="xyz", role="end") 717 718 719 If position is a 2-tuple, these 2 calls are equivalent: 720 721 paragraph.set_bookmark("bookmark", position=(10, 20)) 722 723 and: 724 725 paragraph.set_bookmark("bookmark", position=10, role="start") 726 paragraph.set_bookmark("bookmark", position=20, role="end") 727 728 729 Arguments: 730 731 name -- str 732 733 before -- str regex 734 735 after -- str regex 736 737 position -- int or (int, int) 738 739 role -- None, "start" or "end" 740 741 content -- str regex 742 """ 743 # With "content" => automatically insert a "start" and an "end" 744 # bookmark 745 if ( 746 before is None 747 and after is None 748 and role is None 749 and content is not None 750 and isinstance(position, int) 751 ): 752 # Start 753 start = BookmarkStart(name) 754 self._insert(start, before=content, position=position, main_text=True) 755 # End 756 end = BookmarkEnd(name) 757 self._insert(end, after=content, position=position, main_text=True) 758 return start, end 759 760 # With "(int, int)" => automatically insert a "start" and an "end" 761 # bookmark 762 if ( 763 before is None 764 and after is None 765 and role is None 766 and content is None 767 and isinstance(position, tuple) 768 ): 769 # Start 770 start = BookmarkStart(name) 771 self._insert(start, position=position[0], main_text=True) 772 # End 773 end = BookmarkEnd(name) 774 self._insert(end, position=position[1], main_text=True) 775 return start, end 776 777 # Without "content" nor "position" 778 if content is not None or not isinstance(position, int): 779 raise ValueError("bad arguments") 780 781 # Role 782 if role is None: 783 bookmark: Element = Bookmark(name) 784 elif role == "start": 785 bookmark = BookmarkStart(name) 786 elif role == "end": 787 bookmark = BookmarkEnd(name) 788 else: 789 raise ValueError("bad arguments") 790 791 # Insert 792 self._insert( 793 bookmark, before=before, after=after, position=position, main_text=True 794 ) 795 796 return bookmark
Insert a bookmark before or after the characters in the text which match the regex before/after. When the regex matches more of one part of the text, position can be set to choose which part must be used. If before and after are None, we use only position that is the number of characters.
So, by default, this function inserts a bookmark before the first character of the content. Role can be None, "start" or "end", we insert respectively a position bookmark a bookmark-start or a bookmark-end.
If content is not None these 2 calls are equivalent:
paragraph.set_bookmark("bookmark", content="xyz")
and:
paragraph.set_bookmark("bookmark", before="xyz", role="start") paragraph.set_bookmark("bookmark", after="xyz", role="end")
If position is a 2-tuple, these 2 calls are equivalent:
paragraph.set_bookmark("bookmark", position=(10, 20))
and:
paragraph.set_bookmark("bookmark", position=10, role="start") paragraph.set_bookmark("bookmark", position=20, role="end")
Arguments:
name -- str
before -- str regex
after -- str regex
position -- int or (int, int)
role -- None, "start" or "end"
content -- str regex
Inherited Members
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
831def PageBreak() -> Paragraph: 832 """Return an empty paragraph with a manual page break. 833 834 Using this function requires to register the page break style with: 835 document.add_page_break_style() 836 """ 837 return Paragraph("", style="odfdopagebreak")
Return an empty paragraph with a manual page break.
Using this function requires to register the page break style with: document.add_page_break_style()
146class RectangleShape(ShapeBase): 147 """Create a rectangle shape. 148 149 Arguments: 150 151 style -- str 152 153 text_style -- str 154 155 draw_id -- str 156 157 layer -- str 158 159 position -- (str, str) 160 161 size -- (str, str) 162 163 """ 164 165 _tag = "draw:rect" 166 _properties: tuple[PropDef, ...] = () 167 168 def __init__( 169 self, 170 style: str | None = None, 171 text_style: str | None = None, 172 draw_id: str | None = None, 173 layer: str | None = None, 174 position: tuple | None = None, 175 size: tuple | None = None, 176 **kwargs: Any, 177 ) -> None: 178 kwargs.update( 179 { 180 "style": style, 181 "text_style": text_style, 182 "draw_id": draw_id, 183 "layer": layer, 184 "size": size, 185 "position": position, 186 } 187 ) 188 super().__init__(**kwargs)
Create a rectangle shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
position -- (str, str)
size -- (str, str)
168 def __init__( 169 self, 170 style: str | None = None, 171 text_style: str | None = None, 172 draw_id: str | None = None, 173 layer: str | None = None, 174 position: tuple | None = None, 175 size: tuple | None = None, 176 **kwargs: Any, 177 ) -> None: 178 kwargs.update( 179 { 180 "style": style, 181 "text_style": text_style, 182 "draw_id": draw_id, 183 "layer": layer, 184 "size": size, 185 "position": position, 186 } 187 ) 188 super().__init__(**kwargs)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
59class Reference(Element): 60 """A reference to a content marked by a reference mark. 61 The odf_reference element ("text:reference-ref") represents a field that 62 references a "text:reference-mark-start" or "text:reference-mark" element. 63 Its text:reference-format attribute specifies what is displayed from the 64 referenced element. Default is 'page' 65 Actual content is not updated except for the 'text' format by the 66 update() method. 67 68 69 Creation of references can be tricky, consider using this method: 70 odfdo.paragraph.insert_reference() 71 72 Values for text:reference-format : 73 The defined values for the text:reference-format attribute supported by 74 all reference fields are: 75 - 'chapter': displays the number of the chapter in which the 76 referenced item appears. 77 - 'direction': displays whether the referenced item is above or 78 below the reference field. 79 - 'page': displays the number of the page on which the referenced 80 item appears. 81 - 'text': displays the text of the referenced item. 82 Additional defined values for the text:reference-format attribute 83 supported by references to sequence fields are: 84 - 'caption': displays the caption in which the sequence is used. 85 - 'category-and-value': displays the name and value of the sequence. 86 - 'value': displays the value of the sequence. 87 88 References to bookmarks and other references support additional values, 89 which display the list label of the referenced item. If the referenced 90 item is contained in a list or a numbered paragraph, the list label is 91 the formatted number of the paragraph which contains the referenced 92 item. If the referenced item is not contained in a list or numbered 93 paragraph, the list label is empty, and the referenced field therefore 94 displays nothing. If the referenced bookmark or reference contains more 95 than one paragraph, the list label of the paragraph at which the 96 bookmark or reference starts is taken. 97 98 Additional defined values for the text:reference-format attribute 99 supported by all references to bookmark's or other reference fields 100 are: 101 - 'number': displays the list label of the referenced item. [...] 102 - 'number-all-superior': displays the list label of the referenced 103 item and adds the contents of all list labels of superior levels 104 in front of it. [...] 105 - 'number-no-superior': displays the contents of the list label of 106 the referenced item. 107 """ 108 109 _tag = "text:reference-ref" 110 _properties = (PropDef("name", "text:ref-name"),) 111 format_allowed = ( 112 "chapter", 113 "direction", 114 "page", 115 "text", 116 "caption", 117 "category-and-value", 118 "value", 119 "number", 120 "number-all-superior", 121 "number-no-superior", 122 ) 123 124 def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None: 125 """Create a reference to a content marked by a reference mark. An 126 actual reference mark with the provided name should exist. 127 128 Consider using: odfdo.paragraph.insert_reference() 129 130 The text:ref-name attribute identifies a "text:reference-mark" or 131 "text:referencemark-start" element by the value of that element's 132 text:name attribute. 133 If ref_format is 'text', the current text content of the reference_mark 134 is retrieved. 135 136 Arguments: 137 138 name -- str : name of the reference mark 139 140 ref_format -- str : format of the field. Default is 'page', allowed 141 values are 'chapter', 'direction', 'page', 'text', 142 'caption', 'category-and-value', 'value', 'number', 143 'number-all-superior', 'number-no-superior'. 144 """ 145 super().__init__(**kwargs) 146 if self._do_init: 147 self.name = name 148 self.ref_format = ref_format 149 150 @property 151 def ref_format(self) -> str | None: 152 reference = self.get_attribute("text:reference-format") 153 if isinstance(reference, str): 154 return reference 155 return None 156 157 @ref_format.setter 158 def ref_format(self, ref_format: str) -> None: 159 """Set the text:reference-format attribute. 160 161 Arguments: 162 163 ref_format -- str 164 """ 165 if not ref_format or ref_format not in self.format_allowed: 166 ref_format = "page" 167 self.set_attribute("text:reference-format", ref_format) 168 169 def update(self) -> None: 170 """Update the content of the reference text field. Currently only 171 'text' format is implemented. Other values, for example the 'page' text 172 field, may need to be refreshed through a visual ODF parser. 173 """ 174 ref_format = self.ref_format 175 if ref_format != "text": 176 # only 'text' is implemented 177 return None 178 body = self.document_body 179 if not body: 180 body = self.root 181 name = self.name 182 reference = body.get_reference_mark(name=name) 183 if not reference: 184 return None 185 # we know it is a ReferenceMarkStart: 186 self.text = reference.referenced_text() # type: ignore
A reference to a content marked by a reference mark. The odf_reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its text:reference-format attribute specifies what is displayed from the referenced element. Default is 'page' Actual content is not updated except for the 'text' format by the update() method.
Creation of references can be tricky, consider using this method: odfdo.paragraph.insert_reference()
Values for text:reference-format : The defined values for the text:reference-format attribute supported by all reference fields are: - 'chapter': displays the number of the chapter in which the referenced item appears. - 'direction': displays whether the referenced item is above or below the reference field. - 'page': displays the number of the page on which the referenced item appears. - 'text': displays the text of the referenced item. Additional defined values for the text:reference-format attribute supported by references to sequence fields are: - 'caption': displays the caption in which the sequence is used. - 'category-and-value': displays the name and value of the sequence. - 'value': displays the value of the sequence.
References to bookmarks and other references support additional values,
which display the list label of the referenced item. If the referenced
item is contained in a list or a numbered paragraph, the list label is
the formatted number of the paragraph which contains the referenced
item. If the referenced item is not contained in a list or numbered
paragraph, the list label is empty, and the referenced field therefore
displays nothing. If the referenced bookmark or reference contains more
than one paragraph, the list label of the paragraph at which the
bookmark or reference starts is taken.
Additional defined values for the text:reference-format attribute
supported by all references to bookmark's or other reference fields
are:
- 'number': displays the list label of the referenced item. [...]
- 'number-all-superior': displays the list label of the referenced
item and adds the contents of all list labels of superior levels
in front of it. [...]
- 'number-no-superior': displays the contents of the list label of
the referenced item.
124 def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None: 125 """Create a reference to a content marked by a reference mark. An 126 actual reference mark with the provided name should exist. 127 128 Consider using: odfdo.paragraph.insert_reference() 129 130 The text:ref-name attribute identifies a "text:reference-mark" or 131 "text:referencemark-start" element by the value of that element's 132 text:name attribute. 133 If ref_format is 'text', the current text content of the reference_mark 134 is retrieved. 135 136 Arguments: 137 138 name -- str : name of the reference mark 139 140 ref_format -- str : format of the field. Default is 'page', allowed 141 values are 'chapter', 'direction', 'page', 'text', 142 'caption', 'category-and-value', 'value', 'number', 143 'number-all-superior', 'number-no-superior'. 144 """ 145 super().__init__(**kwargs) 146 if self._do_init: 147 self.name = name 148 self.ref_format = ref_format
Create a reference to a content marked by a reference mark. An actual reference mark with the provided name should exist.
Consider using: odfdo.paragraph.insert_reference()
The text:ref-name attribute identifies a "text:reference-mark" or "text:referencemark-start" element by the value of that element's text:name attribute. If ref_format is 'text', the current text content of the reference_mark is retrieved.
Arguments:
name -- str : name of the reference mark
ref_format -- str : format of the field. Default is 'page', allowed
values are 'chapter', 'direction', 'page', 'text',
'caption', 'category-and-value', 'value', 'number',
'number-all-superior', 'number-no-superior'.
150 @property 151 def ref_format(self) -> str | None: 152 reference = self.get_attribute("text:reference-format") 153 if isinstance(reference, str): 154 return reference 155 return None
Set the text:reference-format attribute.
Arguments:
ref_format -- str
169 def update(self) -> None: 170 """Update the content of the reference text field. Currently only 171 'text' format is implemented. Other values, for example the 'page' text 172 field, may need to be refreshed through a visual ODF parser. 173 """ 174 ref_format = self.ref_format 175 if ref_format != "text": 176 # only 'text' is implemented 177 return None 178 body = self.document_body 179 if not body: 180 body = self.root 181 name = self.name 182 reference = body.get_reference_mark(name=name) 183 if not reference: 184 return None 185 # we know it is a ReferenceMarkStart: 186 self.text = reference.referenced_text() # type: ignore
Update the content of the reference text field. Currently only 'text' format is implemented. Other values, for example the 'page' text field, may need to be refreshed through a visual ODF parser.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
192class ReferenceMark(Element): 193 """A point reference. 194 A point reference marks a position in text and is represented by a single 195 "text:reference-mark" element. 196 """ 197 198 _tag = "text:reference-mark" 199 _properties = (PropDef("name", "text:name"),) 200 201 def __init__(self, name: str = "", **kwargs: Any) -> None: 202 """A point reference. A point reference marks a position in text and is 203 represented by a single "text:reference-mark" element. 204 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 205 206 Arguments: 207 208 name -- str 209 """ 210 super().__init__(**kwargs) 211 if self._do_init: 212 self.name = name
A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element.
201 def __init__(self, name: str = "", **kwargs: Any) -> None: 202 """A point reference. A point reference marks a position in text and is 203 represented by a single "text:reference-mark" element. 204 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 205 206 Arguments: 207 208 name -- str 209 """ 210 super().__init__(**kwargs) 211 if self._do_init: 212 self.name = name
A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element. Consider using the wrapper: odfdo.paragraph.set_reference_mark()
Arguments:
name -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
218class ReferenceMarkEnd(Element): 219 """The "text:reference-mark-end" element represents the end of a range 220 reference. 221 """ 222 223 _tag = "text:reference-mark-end" 224 _properties = (PropDef("name", "text:name"),) 225 226 def __init__(self, name: str = "", **kwargs: Any) -> None: 227 """The "text:reference-mark-end" element represent the end of a range 228 reference. 229 Consider using the wrappers: odfdo.paragraph.set_reference_mark() and 230 odfdo.paragraph.set_reference_mark_end() 231 232 Arguments: 233 234 name -- str 235 """ 236 super().__init__(**kwargs) 237 if self._do_init: 238 self.name = name 239 240 def referenced_text(self) -> str: 241 """Return the text between reference-mark-start and reference-mark-end.""" 242 name = self.name 243 request = ( 244 f"//text()" 245 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 246 f"and following::text:reference-mark-end[@text:name='{name}']]" 247 ) 248 result = " ".join(str(x) for x in self.xpath(request)) 249 return result
The "text:reference-mark-end" element represents the end of a range reference.
226 def __init__(self, name: str = "", **kwargs: Any) -> None: 227 """The "text:reference-mark-end" element represent the end of a range 228 reference. 229 Consider using the wrappers: odfdo.paragraph.set_reference_mark() and 230 odfdo.paragraph.set_reference_mark_end() 231 232 Arguments: 233 234 name -- str 235 """ 236 super().__init__(**kwargs) 237 if self._do_init: 238 self.name = name
The "text:reference-mark-end" element represent the end of a range reference. Consider using the wrappers: odfdo.paragraph.set_reference_mark() and odfdo.paragraph.set_reference_mark_end()
Arguments:
name -- str
240 def referenced_text(self) -> str: 241 """Return the text between reference-mark-start and reference-mark-end.""" 242 name = self.name 243 request = ( 244 f"//text()" 245 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 246 f"and following::text:reference-mark-end[@text:name='{name}']]" 247 ) 248 result = " ".join(str(x) for x in self.xpath(request)) 249 return result
Return the text between reference-mark-start and reference-mark-end.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
255class ReferenceMarkStart(Element): 256 """The "text:reference-mark-start" element represents the start of a 257 range reference. 258 """ 259 260 _tag = "text:reference-mark-start" 261 _properties = (PropDef("name", "text:name"),) 262 263 def __init__(self, name: str = "", **kwargs: Any) -> None: 264 """The "text:reference-mark-start" element represent the start of a range 265 reference. 266 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 267 268 Arguments: 269 270 name -- str 271 """ 272 super().__init__(**kwargs) 273 if self._do_init: 274 self.name = name 275 276 def referenced_text(self) -> str: 277 """Return the text between reference-mark-start and reference-mark-end.""" 278 name = self.name 279 request = ( 280 f"//text()" 281 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 282 f"and following::text:reference-mark-end[@text:name='{name}']]" 283 ) 284 result = " ".join(str(x) for x in self.xpath(request)) 285 return result 286 287 def get_referenced( 288 self, 289 no_header: bool = False, 290 clean: bool = True, 291 as_xml: bool = False, 292 as_list: bool = False, 293 ) -> Element | list | str | None: 294 """Return the document content between the start and end tags of the 295 reference. The content returned by this method can spread over several 296 headers and paragraphs. 297 By default, the content is returned as an "office:text" odf element. 298 299 300 Arguments: 301 302 no_header -- boolean (default to False), translate existing headers 303 tags "text:h" into paragraphs "text:p". 304 305 clean -- boolean (default to True), suppress unwanted tags. Striped 306 tags are : 'text:change', 'text:change-start', 307 'text:change-end', 'text:reference-mark', 308 'text:reference-mark-start', 'text:reference-mark-end'. 309 310 as_xml -- boolean (default to False), format the returned content as 311 a XML string (serialization). 312 313 as_list -- boolean (default to False), do not embed the returned 314 content in a "office:text'" element, instead simply 315 return a raw list of odf elements. 316 """ 317 name = self.name 318 parent = self.parent 319 if parent is None: 320 raise ValueError("Reference need some upper document part") 321 body = self.document_body 322 if not body: 323 body = parent 324 end = body.get_reference_mark_end(name=name) 325 if end is None: 326 raise ValueError("No reference-end found") 327 start = self 328 return _get_referenced(body, start, end, no_header, clean, as_xml, as_list) 329 330 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 331 """Delete the given element from the XML tree. If no element is given, 332 "self" is deleted. The XML library may allow to continue to use an 333 element now "orphan" as long as you have a reference to it. 334 335 For odf_reference_mark_start : delete the reference-end tag if exists. 336 337 Arguments: 338 339 child -- Element 340 341 keep_tail -- boolean (default to True), True for most usages. 342 """ 343 if child is not None: # act like normal delete 344 return super().delete(child, keep_tail) 345 name = self.name 346 parent = self.parent 347 if parent is None: 348 raise ValueError("Can't delete the root element") 349 body = self.document_body 350 if not body: 351 body = parent 352 end = body.get_reference_mark_end(name=name) 353 if end: 354 end.delete() 355 # act like normal delete 356 return super().delete()
The "text:reference-mark-start" element represents the start of a range reference.
263 def __init__(self, name: str = "", **kwargs: Any) -> None: 264 """The "text:reference-mark-start" element represent the start of a range 265 reference. 266 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 267 268 Arguments: 269 270 name -- str 271 """ 272 super().__init__(**kwargs) 273 if self._do_init: 274 self.name = name
The "text:reference-mark-start" element represent the start of a range reference. Consider using the wrapper: odfdo.paragraph.set_reference_mark()
Arguments:
name -- str
276 def referenced_text(self) -> str: 277 """Return the text between reference-mark-start and reference-mark-end.""" 278 name = self.name 279 request = ( 280 f"//text()" 281 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 282 f"and following::text:reference-mark-end[@text:name='{name}']]" 283 ) 284 result = " ".join(str(x) for x in self.xpath(request)) 285 return result
Return the text between reference-mark-start and reference-mark-end.
287 def get_referenced( 288 self, 289 no_header: bool = False, 290 clean: bool = True, 291 as_xml: bool = False, 292 as_list: bool = False, 293 ) -> Element | list | str | None: 294 """Return the document content between the start and end tags of the 295 reference. The content returned by this method can spread over several 296 headers and paragraphs. 297 By default, the content is returned as an "office:text" odf element. 298 299 300 Arguments: 301 302 no_header -- boolean (default to False), translate existing headers 303 tags "text:h" into paragraphs "text:p". 304 305 clean -- boolean (default to True), suppress unwanted tags. Striped 306 tags are : 'text:change', 'text:change-start', 307 'text:change-end', 'text:reference-mark', 308 'text:reference-mark-start', 'text:reference-mark-end'. 309 310 as_xml -- boolean (default to False), format the returned content as 311 a XML string (serialization). 312 313 as_list -- boolean (default to False), do not embed the returned 314 content in a "office:text'" element, instead simply 315 return a raw list of odf elements. 316 """ 317 name = self.name 318 parent = self.parent 319 if parent is None: 320 raise ValueError("Reference need some upper document part") 321 body = self.document_body 322 if not body: 323 body = parent 324 end = body.get_reference_mark_end(name=name) 325 if end is None: 326 raise ValueError("No reference-end found") 327 start = self 328 return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)
Return the document content between the start and end tags of the reference. The content returned by this method can spread over several headers and paragraphs. By default, the content is returned as an "office:text" odf element.
Arguments:
no_header -- boolean (default to False), translate existing headers
tags "text:h" into paragraphs "text:p".
clean -- boolean (default to True), suppress unwanted tags. Striped
tags are : 'text:change', 'text:change-start',
'text:change-end', 'text:reference-mark',
'text:reference-mark-start', 'text:reference-mark-end'.
as_xml -- boolean (default to False), format the returned content as
a XML string (serialization).
as_list -- boolean (default to False), do not embed the returned
content in a "office:text'" element, instead simply
return a raw list of odf elements.
330 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 331 """Delete the given element from the XML tree. If no element is given, 332 "self" is deleted. The XML library may allow to continue to use an 333 element now "orphan" as long as you have a reference to it. 334 335 For odf_reference_mark_start : delete the reference-end tag if exists. 336 337 Arguments: 338 339 child -- Element 340 341 keep_tail -- boolean (default to True), True for most usages. 342 """ 343 if child is not None: # act like normal delete 344 return super().delete(child, keep_tail) 345 name = self.name 346 parent = self.parent 347 if parent is None: 348 raise ValueError("Can't delete the root element") 349 body = self.document_body 350 if not body: 351 body = parent 352 end = body.get_reference_mark_end(name=name) 353 if end: 354 end.delete() 355 # act like normal delete 356 return super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For odf_reference_mark_start : delete the reference-end tag if exists.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
52class Row(Element): 53 """ODF table row "table:table-row" """ 54 55 _tag = "table:table-row" 56 _caching = True 57 _append = Element.append 58 59 def __init__( 60 self, 61 width: int | None = None, 62 repeated: int | None = None, 63 style: str | None = None, 64 **kwargs: Any, 65 ) -> None: 66 """create a Row, optionally filled with "width" number of cells. 67 68 Rows contain cells, their number determine the number of columns. 69 70 You don't generally have to create rows by hand, use the Table API. 71 72 Arguments: 73 74 width -- int 75 76 repeated -- int 77 78 style -- str 79 """ 80 super().__init__(**kwargs) 81 self.y = None 82 if not hasattr(self, "_indexes"): 83 self._indexes = {} 84 self._indexes["_rmap"] = {} 85 if not hasattr(self, "_rmap"): 86 self._compute_row_cache() 87 if not hasattr(self, "_tmap"): 88 self._tmap = [] 89 self._cmap = [] 90 if self._do_init: 91 if width is not None: 92 for _i in range(width): 93 self.append(Cell()) # type:ignore 94 if repeated: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 self._compute_row_cache() 99 100 def _get_cells(self) -> list[Element]: 101 return self.get_elements(_xpath_cell) 102 103 def _translate_row_coordinates( 104 self, 105 coord: tuple | list | str, 106 ) -> tuple[int | None, int | None]: 107 xyzt = convert_coordinates(coord) 108 if len(xyzt) == 2: 109 x, z = xyzt 110 else: 111 x, _, z, __ = xyzt 112 if x and x < 0: 113 x = increment(x, self.width) 114 if z and z < 0: 115 z = increment(z, self.width) 116 return (x, z) 117 118 def _compute_row_cache(self) -> None: 119 idx_repeated_seq = self.elements_repeated_sequence( 120 _xpath_cell, "table:number-columns-repeated" 121 ) 122 self._rmap = make_cache_map(idx_repeated_seq) 123 124 # Public API 125 126 @property 127 def clone(self) -> Row: 128 clone = Element.clone.fget(self) # type: ignore 129 clone.y = self.y 130 if hasattr(self, "_tmap"): 131 if hasattr(self, "_rmap"): 132 clone._rmap = self._rmap[:] 133 clone._tmap = self._tmap[:] 134 clone._cmap = self._cmap[:] 135 return clone 136 137 def _set_repeated(self, repeated: int | None) -> None: 138 """Internal only. Set the numnber of times the row is repeated, or 139 None to delete it. Without changing cache. 140 141 Arguments: 142 143 repeated -- int 144 """ 145 if repeated is None or repeated < 2: 146 with contextlib.suppress(KeyError): 147 self.del_attribute("table:number-rows-repeated") 148 return 149 self.set_attribute("table:number-rows-repeated", str(repeated)) 150 151 @property 152 def repeated(self) -> int | None: 153 """Get / set the number of times the row is repeated. 154 155 Always None when using the table API. 156 157 Return: int or None 158 """ 159 repeated = self.get_attribute("table:number-rows-repeated") 160 if repeated is None: 161 return None 162 return int(repeated) 163 164 @repeated.setter 165 def repeated(self, repeated: int | None) -> None: 166 self._set_repeated(repeated) 167 # update cache 168 current: Element = self 169 while True: 170 # look for Table, parent may be group of rows 171 upper = current.parent 172 if not upper: 173 # lonely row 174 return 175 # parent may be group of rows, not table 176 if isinstance(upper, Element) and upper._tag == "table:table": 177 break 178 current = upper 179 # fixme : need to optimize this 180 if isinstance(upper, Element) and upper._tag == "table:table": 181 upper._compute_table_cache() 182 if hasattr(self, "_tmap"): 183 del self._tmap[:] 184 self._tmap.extend(upper._tmap) 185 else: 186 self._tmap = upper._tmap 187 188 @property 189 def style(self) -> str | None: 190 """Get /set the style of the row itself. 191 192 Return: str 193 """ 194 return self.get_attribute("table:style-name") # type: ignore 195 196 @style.setter 197 def style(self, style: str | Element) -> None: 198 self.set_style_attribute("table:style-name", style) 199 200 @property 201 def width(self) -> int: 202 """Get the number of expected cells in the row, i.e. addition 203 repetitions. 204 205 Return: int 206 """ 207 try: 208 value = self._rmap[-1] + 1 209 except Exception: 210 value = 0 211 return value 212 213 def _translate_x_from_any(self, x: str | int) -> int: 214 return translate_from_any(x, self.width, 0) 215 216 def traverse( # noqa: C901 217 self, 218 start: int | None = None, 219 end: int | None = None, 220 ) -> Iterator[Cell]: 221 """Yield as many cell elements as expected cells in the row, i.e. 222 expand repetitions by returning the same cell as many times as 223 necessary. 224 225 Arguments: 226 227 start -- int 228 229 end -- int 230 231 Copies are returned, use set_cell() to push them back. 232 """ 233 idx = -1 234 before = -1 235 x = 0 236 cell: Cell 237 if start is None and end is None: 238 for juska in self._rmap: 239 idx += 1 240 if idx in self._indexes["_rmap"]: 241 cell = self._indexes["_rmap"][idx] 242 else: 243 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 244 if not isinstance(cell, Cell): 245 raise TypeError(f"Not a cell: {cell!r}") 246 self._indexes["_rmap"][idx] = cell 247 repeated = juska - before 248 before = juska 249 for _i in range(repeated or 1): 250 # Return a copy without the now obsolete repetition 251 if cell is None: 252 cell = Cell() 253 else: 254 cell = cell.clone 255 if repeated > 1: 256 cell.repeated = None 257 cell.y = self.y 258 cell.x = x 259 x += 1 260 yield cell 261 else: 262 if start is None: 263 start = 0 264 start = max(0, start) 265 if end is None: 266 try: 267 end = self._rmap[-1] 268 except Exception: 269 end = -1 270 start_map = find_odf_idx(self._rmap, start) 271 if start_map is None: 272 return 273 if start_map > 0: 274 before = self._rmap[start_map - 1] 275 idx = start_map - 1 276 before = start - 1 277 x = start 278 for juska in self._rmap[start_map:]: 279 idx += 1 280 if idx in self._indexes["_rmap"]: 281 cell = self._indexes["_rmap"][idx] 282 else: 283 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 284 if not isinstance(cell, Cell): 285 raise TypeError(f"Not a cell: {cell!r}") 286 self._indexes["_rmap"][idx] = cell 287 repeated = juska - before 288 before = juska 289 for _i in range(repeated or 1): 290 if x <= end: 291 if cell is None: 292 cell = Cell() 293 else: 294 cell = cell.clone 295 if repeated > 1 or (x == start and start > 0): 296 cell.repeated = None 297 cell.y = self.y 298 cell.x = x 299 x += 1 300 yield cell 301 302 def get_cells( 303 self, 304 coord: str | tuple | None = None, 305 style: str | None = None, 306 content: str | None = None, 307 cell_type: str | None = None, 308 ) -> list[Cell]: 309 """Get the list of cells matching the criteria. 310 311 Filter by cell_type, with cell_type 'all' will retrieve cells of any 312 type, aka non empty cells. 313 314 Filter by coordinates will retrieve the amount of cells defined by 315 'coord', minus the other filters. 316 317 Arguments: 318 319 coord -- str or tuple of int : coordinates 320 321 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 322 'currency', 'percentage' or 'all' 323 324 content -- str regex 325 326 style -- str 327 328 Return: list of Cell 329 """ 330 # fixme : not clones ? 331 if coord: 332 x, z = self._translate_row_coordinates(coord) 333 else: 334 x = None 335 z = None 336 if cell_type: 337 cell_type = cell_type.lower().strip() 338 cells: list[Cell] = [] 339 for cell in self.traverse(start=x, end=z): 340 # Filter the cells by cell_type 341 if cell_type: 342 ctype = cell.type 343 if not ctype or not (ctype == cell_type or cell_type == "all"): 344 continue 345 # Filter the cells with the regex 346 if content and not cell.match(content): 347 continue 348 # Filter the cells with the style 349 if style and style != cell.style: 350 continue 351 cells.append(cell) 352 return cells 353 354 def _get_cell2(self, x: int, clone: bool = True) -> Cell | None: 355 if x >= self.width: 356 return Cell() 357 if clone: 358 return self._get_cell2_base(x).clone # type: ignore 359 else: 360 return self._get_cell2_base(x) 361 362 def _get_cell2_base(self, x: int) -> Cell | None: 363 idx = find_odf_idx(self._rmap, x) 364 cell: Cell 365 if idx is not None: 366 if idx in self._indexes["_rmap"]: 367 cell = self._indexes["_rmap"][idx] 368 else: 369 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 370 self._indexes["_rmap"][idx] = cell 371 return cell 372 return None 373 374 def get_cell(self, x: int, clone: bool = True) -> Cell | None: 375 """Get the cell at position "x" starting from 0. Alphabetical 376 positions like "D" are accepted. 377 378 A copy is returned, use set_cell() to push it back. 379 380 Arguments: 381 382 x -- int or str 383 384 Return: Cell | None 385 """ 386 x = self._translate_x_from_any(x) 387 cell = self._get_cell2(x, clone=clone) 388 if not cell: 389 return None 390 cell.y = self.y 391 cell.x = x 392 return cell 393 394 def get_value( 395 self, 396 x: int | str, 397 get_type: bool = False, 398 ) -> Any | tuple[Any, str]: 399 """Shortcut to get the value of the cell at position "x". 400 If get_type is True, returns the tuples (value, ODF type). 401 402 If the cell is empty, returns None or (None, None) 403 404 See get_cell() and Cell.get_value(). 405 """ 406 if get_type: 407 x = self._translate_x_from_any(x) 408 cell = self._get_cell2_base(x) 409 if cell is None: 410 return (None, None) 411 return cell.get_value(get_type=get_type) 412 x = self._translate_x_from_any(x) 413 cell = self._get_cell2_base(x) 414 if cell is None: 415 return None 416 return cell.get_value() 417 418 def set_cell( 419 self, 420 x: int | str, 421 cell: Cell | None = None, 422 clone: bool = True, 423 ) -> Cell: 424 """Push the cell back in the row at position "x" starting from 0. 425 Alphabetical positions like "D" are accepted. 426 427 Arguments: 428 429 x -- int or str 430 431 returns the cell with x and y updated 432 """ 433 cell_back: Cell 434 if cell is None: 435 cell = Cell() 436 repeated = 1 437 clone = False 438 else: 439 repeated = cell.repeated or 1 440 x = self._translate_x_from_any(x) 441 # Outside the defined row 442 diff = x - self.width 443 if diff == 0: 444 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 445 elif diff > 0: 446 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 447 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 448 else: 449 # Inside the defined row 450 set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone) 451 cell.x = x 452 cell.y = self.y 453 cell_back = cell 454 return cell_back 455 456 def set_value( 457 self, 458 x: int | str, 459 value: Any, 460 style: str | None = None, 461 cell_type: str | None = None, 462 currency: str | None = None, 463 ) -> None: 464 """Shortcut to set the value of the cell at position "x". 465 466 Arguments: 467 468 x -- int or str 469 470 value -- Python type 471 472 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 473 'string' or 'time' 474 475 currency -- three-letter str 476 477 style -- str 478 479 See get_cell() and Cell.get_value(). 480 """ 481 self.set_cell( 482 x, 483 Cell(value, style=style, cell_type=cell_type, currency=currency), 484 clone=False, 485 ) 486 487 def insert_cell( 488 self, 489 x: int | str, 490 cell: Cell | None = None, 491 clone: bool = True, 492 ) -> Cell: 493 """Insert the given cell at position "x" starting from 0. If no cell 494 is given, an empty one is created. 495 496 Alphabetical positions like "D" are accepted. 497 498 Do not use when working on a table, use Table.insert_cell(). 499 500 Arguments: 501 502 x -- int or str 503 504 cell -- Cell 505 506 returns the cell with x and y updated 507 """ 508 cell_back: Cell 509 if cell is None: 510 cell = Cell() 511 x = self._translate_x_from_any(x) 512 # Outside the defined row 513 diff = x - self.width 514 if diff < 0: 515 insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap") 516 cell.x = x 517 cell.y = self.y 518 cell_back = cell 519 elif diff == 0: 520 cell_back = self.append_cell(cell, clone=clone) 521 else: 522 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 523 cell_back = self.append_cell(cell, clone=clone) 524 return cell_back 525 526 def extend_cells(self, cells: Iterable[Cell] | None = None) -> None: 527 if cells is None: 528 cells = [] 529 self.extend(cells) 530 self._compute_row_cache() 531 532 def append_cell( 533 self, 534 cell: Cell | None = None, 535 clone: bool = True, 536 _repeated: int | None = None, 537 ) -> Cell: 538 """Append the given cell at the end of the row. Repeated cells are 539 accepted. If no cell is given, an empty one is created. 540 541 Do not use when working on a table, use Table.append_cell(). 542 543 Arguments: 544 545 cell -- Cell 546 547 _repeated -- (optional), repeated value of the row 548 549 returns the cell with x and y updated 550 """ 551 if cell is None: 552 cell = Cell() 553 clone = False 554 if clone: 555 cell = cell.clone 556 self._append(cell) 557 if _repeated is None: 558 _repeated = cell.repeated or 1 559 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 560 cell.x = self.width - 1 561 cell.y = self.y 562 return cell 563 564 # fix for unit test and typos 565 append = append_cell # type: ignore 566 567 def delete_cell(self, x: int | str) -> None: 568 """Delete the cell at the given position "x" starting from 0. 569 Alphabetical positions like "D" are accepted. 570 571 Cells on the right will be shifted to the left. In a table, other 572 rows remain unaffected. 573 574 Arguments: 575 576 x -- int or str 577 """ 578 x = self._translate_x_from_any(x) 579 if x >= self.width: 580 return 581 delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap") 582 583 def get_values( 584 self, 585 coord: str | tuple | None = None, 586 cell_type: str | None = None, 587 complete: bool = False, 588 get_type: bool = False, 589 ) -> list[Any | tuple[Any, Any]]: 590 """Shortcut to get the cell values in this row. 591 592 Filter by cell_type, with cell_type 'all' will retrieve cells of any 593 type, aka non empty cells. 594 If cell_type is used and complete is True, missing values are 595 replaced by None. 596 If cell_type is None, complete is always True : with no cell type 597 queried, get_values() returns None for each empty cell, the length 598 of the list is equal to the length of the row (depending on 599 coordinates use). 600 601 If get_type is True, returns a tuple (value, ODF type of value), or 602 (None, None) for empty cells if complete is True. 603 604 Filter by coordinates will retrieve the amount of cells defined by 605 coordinates with None for empty cells, except when using cell_type. 606 607 608 Arguments: 609 610 coord -- str or tuple of int : coordinates in row 611 612 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 613 'currency', 'percentage' or 'all' 614 615 complete -- boolean 616 617 get_type -- boolean 618 619 Return: list of Python types, or list of tuples. 620 """ 621 if coord: 622 x, z = self._translate_row_coordinates(coord) 623 else: 624 x = None 625 z = None 626 if cell_type: 627 cell_type = cell_type.lower().strip() 628 values: list[Any | tuple[Any, Any]] = [] 629 for cell in self.traverse(start=x, end=z): 630 # Filter the cells by cell_type 631 ctype = cell.type 632 if not ctype or not (ctype == cell_type or cell_type == "all"): 633 if complete: 634 if get_type: 635 values.append((None, None)) 636 else: 637 values.append(None) 638 continue 639 values.append(cell.get_value(get_type=get_type)) 640 return values 641 else: 642 return [ 643 cell.get_value(get_type=get_type) 644 for cell in self.traverse(start=x, end=z) 645 ] 646 647 def set_cells( 648 self, 649 cells: list[Cell] | tuple[Cell] | None = None, 650 start: int | str = 0, 651 clone: bool = True, 652 ) -> None: 653 """Set the cells in the row, from the 'start' column. 654 This method does not clear the row, use row.clear() before to start 655 with an empty row. 656 657 Arguments: 658 659 cells -- list of cells 660 661 start -- int or str 662 """ 663 if cells is None: 664 cells = [] 665 if start is None: 666 start = 0 667 else: 668 start = self._translate_x_from_any(start) 669 if start == 0 and clone is False and (len(cells) >= self.width): 670 self.clear() 671 self.extend_cells(cells) 672 else: 673 x = start 674 for cell in cells: 675 self.set_cell(x, cell, clone=clone) 676 if cell: 677 x += cell.repeated or 1 678 else: 679 x += 1 680 681 def set_values( 682 self, 683 values: list[Any], 684 start: int | str = 0, 685 style: str | None = None, 686 cell_type: str | None = None, 687 currency: str | None = None, 688 ) -> None: 689 """Shortcut to set the value of cells in the row, from the 'start' 690 column vith values. 691 This method does not clear the row, use row.clear() before to start 692 with an empty row. 693 694 Arguments: 695 696 values -- list of Python types 697 698 start -- int or str 699 700 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 701 'currency' or 'percentage' 702 703 currency -- three-letter str 704 705 style -- cell style 706 """ 707 # fixme : if values n, n+ are same, use repeat 708 if start is None: 709 start = 0 710 else: 711 start = self._translate_x_from_any(start) 712 if start == 0 and (len(values) >= self.width): 713 self.clear() 714 cells = [ 715 Cell(value, style=style, cell_type=cell_type, currency=currency) 716 for value in values 717 ] 718 self.extend_cells(cells) 719 else: 720 x = start 721 for value in values: 722 self.set_cell( 723 x, 724 Cell(value, style=style, cell_type=cell_type, currency=currency), 725 clone=False, 726 ) 727 x += 1 728 729 def rstrip(self, aggressive: bool = False) -> None: 730 """Remove *in-place* empty cells at the right of the row. An empty 731 cell has no value but can have style. If "aggressive" is True, style 732 is ignored. 733 734 Arguments: 735 736 aggressive -- bool 737 """ 738 for cell in reversed(self._get_cells()): 739 if not cell.is_empty(aggressive=aggressive): # type: ignore 740 break 741 self.delete(cell) 742 self._compute_row_cache() 743 self._indexes["_rmap"] = {} 744 745 def is_empty(self, aggressive: bool = False) -> bool: 746 """Return whether every cell in the row has no value or the value 747 evaluates to False (empty string), and no style. 748 749 If aggressive is True, empty cells with style are considered empty. 750 751 Arguments: 752 753 aggressive -- bool 754 755 Return: bool 756 """ 757 return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells()) # type: ignore
ODF table row "table:table-row"
59 def __init__( 60 self, 61 width: int | None = None, 62 repeated: int | None = None, 63 style: str | None = None, 64 **kwargs: Any, 65 ) -> None: 66 """create a Row, optionally filled with "width" number of cells. 67 68 Rows contain cells, their number determine the number of columns. 69 70 You don't generally have to create rows by hand, use the Table API. 71 72 Arguments: 73 74 width -- int 75 76 repeated -- int 77 78 style -- str 79 """ 80 super().__init__(**kwargs) 81 self.y = None 82 if not hasattr(self, "_indexes"): 83 self._indexes = {} 84 self._indexes["_rmap"] = {} 85 if not hasattr(self, "_rmap"): 86 self._compute_row_cache() 87 if not hasattr(self, "_tmap"): 88 self._tmap = [] 89 self._cmap = [] 90 if self._do_init: 91 if width is not None: 92 for _i in range(width): 93 self.append(Cell()) # type:ignore 94 if repeated: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 self._compute_row_cache()
create a Row, optionally filled with "width" number of cells.
Rows contain cells, their number determine the number of columns.
You don't generally have to create rows by hand, use the Table API.
Arguments:
width -- int
repeated -- int
style -- str
151 @property 152 def repeated(self) -> int | None: 153 """Get / set the number of times the row is repeated. 154 155 Always None when using the table API. 156 157 Return: int or None 158 """ 159 repeated = self.get_attribute("table:number-rows-repeated") 160 if repeated is None: 161 return None 162 return int(repeated)
Get / set the number of times the row is repeated.
Always None when using the table API.
Return: int or None
188 @property 189 def style(self) -> str | None: 190 """Get /set the style of the row itself. 191 192 Return: str 193 """ 194 return self.get_attribute("table:style-name") # type: ignore
Get /set the style of the row itself.
Return: str
200 @property 201 def width(self) -> int: 202 """Get the number of expected cells in the row, i.e. addition 203 repetitions. 204 205 Return: int 206 """ 207 try: 208 value = self._rmap[-1] + 1 209 except Exception: 210 value = 0 211 return value
Get the number of expected cells in the row, i.e. addition repetitions.
Return: int
216 def traverse( # noqa: C901 217 self, 218 start: int | None = None, 219 end: int | None = None, 220 ) -> Iterator[Cell]: 221 """Yield as many cell elements as expected cells in the row, i.e. 222 expand repetitions by returning the same cell as many times as 223 necessary. 224 225 Arguments: 226 227 start -- int 228 229 end -- int 230 231 Copies are returned, use set_cell() to push them back. 232 """ 233 idx = -1 234 before = -1 235 x = 0 236 cell: Cell 237 if start is None and end is None: 238 for juska in self._rmap: 239 idx += 1 240 if idx in self._indexes["_rmap"]: 241 cell = self._indexes["_rmap"][idx] 242 else: 243 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 244 if not isinstance(cell, Cell): 245 raise TypeError(f"Not a cell: {cell!r}") 246 self._indexes["_rmap"][idx] = cell 247 repeated = juska - before 248 before = juska 249 for _i in range(repeated or 1): 250 # Return a copy without the now obsolete repetition 251 if cell is None: 252 cell = Cell() 253 else: 254 cell = cell.clone 255 if repeated > 1: 256 cell.repeated = None 257 cell.y = self.y 258 cell.x = x 259 x += 1 260 yield cell 261 else: 262 if start is None: 263 start = 0 264 start = max(0, start) 265 if end is None: 266 try: 267 end = self._rmap[-1] 268 except Exception: 269 end = -1 270 start_map = find_odf_idx(self._rmap, start) 271 if start_map is None: 272 return 273 if start_map > 0: 274 before = self._rmap[start_map - 1] 275 idx = start_map - 1 276 before = start - 1 277 x = start 278 for juska in self._rmap[start_map:]: 279 idx += 1 280 if idx in self._indexes["_rmap"]: 281 cell = self._indexes["_rmap"][idx] 282 else: 283 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 284 if not isinstance(cell, Cell): 285 raise TypeError(f"Not a cell: {cell!r}") 286 self._indexes["_rmap"][idx] = cell 287 repeated = juska - before 288 before = juska 289 for _i in range(repeated or 1): 290 if x <= end: 291 if cell is None: 292 cell = Cell() 293 else: 294 cell = cell.clone 295 if repeated > 1 or (x == start and start > 0): 296 cell.repeated = None 297 cell.y = self.y 298 cell.x = x 299 x += 1 300 yield cell
Yield as many cell elements as expected cells in the row, i.e. expand repetitions by returning the same cell as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_cell() to push them back.
302 def get_cells( 303 self, 304 coord: str | tuple | None = None, 305 style: str | None = None, 306 content: str | None = None, 307 cell_type: str | None = None, 308 ) -> list[Cell]: 309 """Get the list of cells matching the criteria. 310 311 Filter by cell_type, with cell_type 'all' will retrieve cells of any 312 type, aka non empty cells. 313 314 Filter by coordinates will retrieve the amount of cells defined by 315 'coord', minus the other filters. 316 317 Arguments: 318 319 coord -- str or tuple of int : coordinates 320 321 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 322 'currency', 'percentage' or 'all' 323 324 content -- str regex 325 326 style -- str 327 328 Return: list of Cell 329 """ 330 # fixme : not clones ? 331 if coord: 332 x, z = self._translate_row_coordinates(coord) 333 else: 334 x = None 335 z = None 336 if cell_type: 337 cell_type = cell_type.lower().strip() 338 cells: list[Cell] = [] 339 for cell in self.traverse(start=x, end=z): 340 # Filter the cells by cell_type 341 if cell_type: 342 ctype = cell.type 343 if not ctype or not (ctype == cell_type or cell_type == "all"): 344 continue 345 # Filter the cells with the regex 346 if content and not cell.match(content): 347 continue 348 # Filter the cells with the style 349 if style and style != cell.style: 350 continue 351 cells.append(cell) 352 return cells
Get the list of cells matching the criteria.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.
Filter by coordinates will retrieve the amount of cells defined by 'coord', minus the other filters.
Arguments:
coord -- str or tuple of int : coordinates
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
Return: list of Cell
374 def get_cell(self, x: int, clone: bool = True) -> Cell | None: 375 """Get the cell at position "x" starting from 0. Alphabetical 376 positions like "D" are accepted. 377 378 A copy is returned, use set_cell() to push it back. 379 380 Arguments: 381 382 x -- int or str 383 384 Return: Cell | None 385 """ 386 x = self._translate_x_from_any(x) 387 cell = self._get_cell2(x, clone=clone) 388 if not cell: 389 return None 390 cell.y = self.y 391 cell.x = x 392 return cell
Get the cell at position "x" starting from 0. Alphabetical positions like "D" are accepted.
A copy is returned, use set_cell() to push it back.
Arguments:
x -- int or str
Return: Cell | None
394 def get_value( 395 self, 396 x: int | str, 397 get_type: bool = False, 398 ) -> Any | tuple[Any, str]: 399 """Shortcut to get the value of the cell at position "x". 400 If get_type is True, returns the tuples (value, ODF type). 401 402 If the cell is empty, returns None or (None, None) 403 404 See get_cell() and Cell.get_value(). 405 """ 406 if get_type: 407 x = self._translate_x_from_any(x) 408 cell = self._get_cell2_base(x) 409 if cell is None: 410 return (None, None) 411 return cell.get_value(get_type=get_type) 412 x = self._translate_x_from_any(x) 413 cell = self._get_cell2_base(x) 414 if cell is None: 415 return None 416 return cell.get_value()
Shortcut to get the value of the cell at position "x". If get_type is True, returns the tuples (value, ODF type).
If the cell is empty, returns None or (None, None)
See get_cell() and Cell.get_value().
418 def set_cell( 419 self, 420 x: int | str, 421 cell: Cell | None = None, 422 clone: bool = True, 423 ) -> Cell: 424 """Push the cell back in the row at position "x" starting from 0. 425 Alphabetical positions like "D" are accepted. 426 427 Arguments: 428 429 x -- int or str 430 431 returns the cell with x and y updated 432 """ 433 cell_back: Cell 434 if cell is None: 435 cell = Cell() 436 repeated = 1 437 clone = False 438 else: 439 repeated = cell.repeated or 1 440 x = self._translate_x_from_any(x) 441 # Outside the defined row 442 diff = x - self.width 443 if diff == 0: 444 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 445 elif diff > 0: 446 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 447 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 448 else: 449 # Inside the defined row 450 set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone) 451 cell.x = x 452 cell.y = self.y 453 cell_back = cell 454 return cell_back
Push the cell back in the row at position "x" starting from 0. Alphabetical positions like "D" are accepted.
Arguments:
x -- int or str
returns the cell with x and y updated
456 def set_value( 457 self, 458 x: int | str, 459 value: Any, 460 style: str | None = None, 461 cell_type: str | None = None, 462 currency: str | None = None, 463 ) -> None: 464 """Shortcut to set the value of the cell at position "x". 465 466 Arguments: 467 468 x -- int or str 469 470 value -- Python type 471 472 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 473 'string' or 'time' 474 475 currency -- three-letter str 476 477 style -- str 478 479 See get_cell() and Cell.get_value(). 480 """ 481 self.set_cell( 482 x, 483 Cell(value, style=style, cell_type=cell_type, currency=currency), 484 clone=False, 485 )
Shortcut to set the value of the cell at position "x".
Arguments:
x -- int or str
value -- Python type
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
See get_cell() and Cell.get_value().
487 def insert_cell( 488 self, 489 x: int | str, 490 cell: Cell | None = None, 491 clone: bool = True, 492 ) -> Cell: 493 """Insert the given cell at position "x" starting from 0. If no cell 494 is given, an empty one is created. 495 496 Alphabetical positions like "D" are accepted. 497 498 Do not use when working on a table, use Table.insert_cell(). 499 500 Arguments: 501 502 x -- int or str 503 504 cell -- Cell 505 506 returns the cell with x and y updated 507 """ 508 cell_back: Cell 509 if cell is None: 510 cell = Cell() 511 x = self._translate_x_from_any(x) 512 # Outside the defined row 513 diff = x - self.width 514 if diff < 0: 515 insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap") 516 cell.x = x 517 cell.y = self.y 518 cell_back = cell 519 elif diff == 0: 520 cell_back = self.append_cell(cell, clone=clone) 521 else: 522 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 523 cell_back = self.append_cell(cell, clone=clone) 524 return cell_back
Insert the given cell at position "x" starting from 0. If no cell is given, an empty one is created.
Alphabetical positions like "D" are accepted.
Do not use when working on a table, use Table.insert_cell().
Arguments:
x -- int or str
cell -- Cell
returns the cell with x and y updated
532 def append_cell( 533 self, 534 cell: Cell | None = None, 535 clone: bool = True, 536 _repeated: int | None = None, 537 ) -> Cell: 538 """Append the given cell at the end of the row. Repeated cells are 539 accepted. If no cell is given, an empty one is created. 540 541 Do not use when working on a table, use Table.append_cell(). 542 543 Arguments: 544 545 cell -- Cell 546 547 _repeated -- (optional), repeated value of the row 548 549 returns the cell with x and y updated 550 """ 551 if cell is None: 552 cell = Cell() 553 clone = False 554 if clone: 555 cell = cell.clone 556 self._append(cell) 557 if _repeated is None: 558 _repeated = cell.repeated or 1 559 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 560 cell.x = self.width - 1 561 cell.y = self.y 562 return cell
Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.
Do not use when working on a table, use Table.append_cell().
Arguments:
cell -- Cell
_repeated -- (optional), repeated value of the row
returns the cell with x and y updated
532 def append_cell( 533 self, 534 cell: Cell | None = None, 535 clone: bool = True, 536 _repeated: int | None = None, 537 ) -> Cell: 538 """Append the given cell at the end of the row. Repeated cells are 539 accepted. If no cell is given, an empty one is created. 540 541 Do not use when working on a table, use Table.append_cell(). 542 543 Arguments: 544 545 cell -- Cell 546 547 _repeated -- (optional), repeated value of the row 548 549 returns the cell with x and y updated 550 """ 551 if cell is None: 552 cell = Cell() 553 clone = False 554 if clone: 555 cell = cell.clone 556 self._append(cell) 557 if _repeated is None: 558 _repeated = cell.repeated or 1 559 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 560 cell.x = self.width - 1 561 cell.y = self.y 562 return cell
Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.
Do not use when working on a table, use Table.append_cell().
Arguments:
cell -- Cell
_repeated -- (optional), repeated value of the row
returns the cell with x and y updated
567 def delete_cell(self, x: int | str) -> None: 568 """Delete the cell at the given position "x" starting from 0. 569 Alphabetical positions like "D" are accepted. 570 571 Cells on the right will be shifted to the left. In a table, other 572 rows remain unaffected. 573 574 Arguments: 575 576 x -- int or str 577 """ 578 x = self._translate_x_from_any(x) 579 if x >= self.width: 580 return 581 delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")
Delete the cell at the given position "x" starting from 0. Alphabetical positions like "D" are accepted.
Cells on the right will be shifted to the left. In a table, other rows remain unaffected.
Arguments:
x -- int or str
583 def get_values( 584 self, 585 coord: str | tuple | None = None, 586 cell_type: str | None = None, 587 complete: bool = False, 588 get_type: bool = False, 589 ) -> list[Any | tuple[Any, Any]]: 590 """Shortcut to get the cell values in this row. 591 592 Filter by cell_type, with cell_type 'all' will retrieve cells of any 593 type, aka non empty cells. 594 If cell_type is used and complete is True, missing values are 595 replaced by None. 596 If cell_type is None, complete is always True : with no cell type 597 queried, get_values() returns None for each empty cell, the length 598 of the list is equal to the length of the row (depending on 599 coordinates use). 600 601 If get_type is True, returns a tuple (value, ODF type of value), or 602 (None, None) for empty cells if complete is True. 603 604 Filter by coordinates will retrieve the amount of cells defined by 605 coordinates with None for empty cells, except when using cell_type. 606 607 608 Arguments: 609 610 coord -- str or tuple of int : coordinates in row 611 612 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 613 'currency', 'percentage' or 'all' 614 615 complete -- boolean 616 617 get_type -- boolean 618 619 Return: list of Python types, or list of tuples. 620 """ 621 if coord: 622 x, z = self._translate_row_coordinates(coord) 623 else: 624 x = None 625 z = None 626 if cell_type: 627 cell_type = cell_type.lower().strip() 628 values: list[Any | tuple[Any, Any]] = [] 629 for cell in self.traverse(start=x, end=z): 630 # Filter the cells by cell_type 631 ctype = cell.type 632 if not ctype or not (ctype == cell_type or cell_type == "all"): 633 if complete: 634 if get_type: 635 values.append((None, None)) 636 else: 637 values.append(None) 638 continue 639 values.append(cell.get_value(get_type=get_type)) 640 return values 641 else: 642 return [ 643 cell.get_value(get_type=get_type) 644 for cell in self.traverse(start=x, end=z) 645 ]
Shortcut to get the cell values in this row.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type is used and complete is True, missing values are replaced by None. If cell_type is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length of the list is equal to the length of the row (depending on coordinates use).
If get_type is True, returns a tuple (value, ODF type of value), or (None, None) for empty cells if complete is True.
Filter by coordinates will retrieve the amount of cells defined by coordinates with None for empty cells, except when using cell_type.
Arguments:
coord -- str or tuple of int : coordinates in row
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of Python types, or list of tuples.
647 def set_cells( 648 self, 649 cells: list[Cell] | tuple[Cell] | None = None, 650 start: int | str = 0, 651 clone: bool = True, 652 ) -> None: 653 """Set the cells in the row, from the 'start' column. 654 This method does not clear the row, use row.clear() before to start 655 with an empty row. 656 657 Arguments: 658 659 cells -- list of cells 660 661 start -- int or str 662 """ 663 if cells is None: 664 cells = [] 665 if start is None: 666 start = 0 667 else: 668 start = self._translate_x_from_any(start) 669 if start == 0 and clone is False and (len(cells) >= self.width): 670 self.clear() 671 self.extend_cells(cells) 672 else: 673 x = start 674 for cell in cells: 675 self.set_cell(x, cell, clone=clone) 676 if cell: 677 x += cell.repeated or 1 678 else: 679 x += 1
Set the cells in the row, from the 'start' column. This method does not clear the row, use row.clear() before to start with an empty row.
Arguments:
cells -- list of cells
start -- int or str
681 def set_values( 682 self, 683 values: list[Any], 684 start: int | str = 0, 685 style: str | None = None, 686 cell_type: str | None = None, 687 currency: str | None = None, 688 ) -> None: 689 """Shortcut to set the value of cells in the row, from the 'start' 690 column vith values. 691 This method does not clear the row, use row.clear() before to start 692 with an empty row. 693 694 Arguments: 695 696 values -- list of Python types 697 698 start -- int or str 699 700 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 701 'currency' or 'percentage' 702 703 currency -- three-letter str 704 705 style -- cell style 706 """ 707 # fixme : if values n, n+ are same, use repeat 708 if start is None: 709 start = 0 710 else: 711 start = self._translate_x_from_any(start) 712 if start == 0 and (len(values) >= self.width): 713 self.clear() 714 cells = [ 715 Cell(value, style=style, cell_type=cell_type, currency=currency) 716 for value in values 717 ] 718 self.extend_cells(cells) 719 else: 720 x = start 721 for value in values: 722 self.set_cell( 723 x, 724 Cell(value, style=style, cell_type=cell_type, currency=currency), 725 clone=False, 726 ) 727 x += 1
Shortcut to set the value of cells in the row, from the 'start' column vith values. This method does not clear the row, use row.clear() before to start with an empty row.
Arguments:
values -- list of Python types
start -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency' or 'percentage'
currency -- three-letter str
style -- cell style
729 def rstrip(self, aggressive: bool = False) -> None: 730 """Remove *in-place* empty cells at the right of the row. An empty 731 cell has no value but can have style. If "aggressive" is True, style 732 is ignored. 733 734 Arguments: 735 736 aggressive -- bool 737 """ 738 for cell in reversed(self._get_cells()): 739 if not cell.is_empty(aggressive=aggressive): # type: ignore 740 break 741 self.delete(cell) 742 self._compute_row_cache() 743 self._indexes["_rmap"] = {}
Remove in-place empty cells at the right of the row. An empty cell has no value but can have style. If "aggressive" is True, style is ignored.
Arguments:
aggressive -- bool
745 def is_empty(self, aggressive: bool = False) -> bool: 746 """Return whether every cell in the row has no value or the value 747 evaluates to False (empty string), and no style. 748 749 If aggressive is True, empty cells with style are considered empty. 750 751 Arguments: 752 753 aggressive -- bool 754 755 Return: bool 756 """ 757 return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells()) # type: ignore
Return whether every cell in the row has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
Return: bool
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
133class RowGroup(Element): 134 """ "table:table-row-group" group rows with common properties.""" 135 136 # TODO 137 _tag = "table:table-row-group" 138 _caching = True 139 140 def __init__( 141 self, 142 height: int | None = None, 143 width: int | None = None, 144 **kwargs: Any, 145 ) -> None: 146 """Create a group of rows, optionnaly filled with "height" number of 147 rows, of "width" cells each. 148 149 Row group bear style information applied to a series of rows. 150 151 Arguments: 152 153 height -- int 154 155 width -- int 156 """ 157 super().__init__(**kwargs) 158 if self._do_init and height is not None: 159 for _i in range(height): 160 row = Row(width=width) 161 self.append(row)
"table:table-row-group" group rows with common properties.
140 def __init__( 141 self, 142 height: int | None = None, 143 width: int | None = None, 144 **kwargs: Any, 145 ) -> None: 146 """Create a group of rows, optionnaly filled with "height" number of 147 rows, of "width" cells each. 148 149 Row group bear style information applied to a series of rows. 150 151 Arguments: 152 153 height -- int 154 155 width -- int 156 """ 157 super().__init__(**kwargs) 158 if self._do_init and height is not None: 159 for _i in range(height): 160 row = Row(width=width) 161 self.append(row)
Create a group of rows, optionnaly filled with "height" number of rows, of "width" cells each.
Row group bear style information applied to a series of rows.
Arguments:
height -- int
width -- int
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
32class Section(Element): 33 """ODF section "text:section" 34 35 Arguments: 36 37 style -- str 38 39 name -- str 40 """ 41 42 _tag = "text:section" 43 _properties = ( 44 PropDef("style", "text:style-name"), 45 PropDef("name", "text:name"), 46 ) 47 48 def __init__( 49 self, 50 style: str | None = None, 51 name: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 super().__init__(**kwargs) 55 if self._do_init: 56 if style: 57 self.style = style 58 if name: 59 self.name = name 60 61 def get_formatted_text(self, context: dict | None = None) -> str: 62 result = [element.get_formatted_text(context) for element in self.children] 63 result.append("\n") 64 return "".join(result)
ODF section "text:section"
Arguments:
style -- str
name -- str
61 def get_formatted_text(self, context: dict | None = None) -> str: 62 result = [element.get_formatted_text(context) for element in self.children] 63 result.append("\n") 64 return "".join(result)
This function should return a beautiful version of the text.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
169class Spacer(Element): 170 """This element shall be used to represent the second and all following “ “ 171 (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. 172 Note: It is not an error if the character preceding the element is not a 173 white space character, but it is good practice to use this element only for 174 the second and all following SPACE characters in a sequence. 175 """ 176 177 _tag = "text:s" 178 _properties: tuple[PropDef, ...] = (PropDef("number", "text:c"),) 179 180 def __init__(self, number: int = 1, **kwargs: Any): 181 """ 182 Arguments: 183 184 number -- int 185 """ 186 super().__init__(**kwargs) 187 if self._do_init: 188 self.number = str(number)
This element shall be used to represent the second and all following “ “ (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. Note: It is not an error if the character preceding the element is not a white space character, but it is good practice to use this element only for the second and all following SPACE characters in a sequence.
180 def __init__(self, number: int = 1, **kwargs: Any): 181 """ 182 Arguments: 183 184 number -- int 185 """ 186 super().__init__(**kwargs) 187 if self._do_init: 188 self.number = str(number)
Arguments:
number -- int
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
799class Span(Paragraph): 800 """Create a span element "text:span" of the given style containing the optional 801 given text. 802 """ 803 804 _tag = "text:span" 805 _properties = ( 806 PropDef("style", "text:style-name"), 807 PropDef("class_names", "text:class-names"), 808 ) 809 810 def __init__( 811 self, 812 text: str | None = None, 813 style: str | None = None, 814 **kwargs: Any, 815 ) -> None: 816 """ 817 Arguments: 818 819 text -- str 820 821 style -- str 822 """ 823 super().__init__(**kwargs) 824 if self._do_init: 825 if text: 826 self.text = text 827 if style: 828 self.style = style
Create a span element "text:span" of the given style containing the optional given text.
810 def __init__( 811 self, 812 text: str | None = None, 813 style: str | None = None, 814 **kwargs: Any, 815 ) -> None: 816 """ 817 Arguments: 818 819 text -- str 820 821 style -- str 822 """ 823 super().__init__(**kwargs) 824 if self._do_init: 825 if text: 826 self.text = text 827 if style: 828 self.style = style
Arguments:
text -- str
style -- str
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Paragraph
- insert_note
- insert_annotation
- insert_annotation_end
- set_reference_mark
- set_reference_mark_end
- insert_variable
- set_span
- remove_spans
- remove_span
- set_link
- remove_links
- remove_link
- insert_reference
- set_bookmark
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
340class Style(Element): 341 """Style class for all these tags: 342 343 'style:style' 344 'number:date-style', 345 'number:number-style', 346 'number:percentage-style', 347 'number:time-style' 348 'style:font-face', 349 'style:master-page', 350 'style:page-layout', 351 'style:presentation-page-layout', 352 'text:list-style', 353 'text:outline-style', 354 'style:tab-stops', 355 ... 356 """ 357 358 _properties: tuple[PropDef, ...] = ( 359 PropDef("page_layout", "style:page-layout-name", "master-page"), 360 PropDef("next_style", "style:next-style-name", "master-page"), 361 PropDef("name", "style:name"), 362 PropDef("parent_style", "style:parent-style-name"), 363 PropDef("display_name", "style:display-name"), 364 PropDef("svg_font_family", "svg:font-family"), 365 PropDef("font_family_generic", "style:font-family-generic"), 366 PropDef("font_pitch", "style:font-pitch"), 367 PropDef("text_style", "text:style-name"), 368 PropDef("master_page", "style:master-page-name", "paragraph"), 369 PropDef("master_page", "style:master-page-name", "paragraph"), 370 PropDef("master_page", "style:master-page-name", "paragraph"), 371 # style:tab-stop 372 PropDef("style_type", "style:type"), 373 PropDef("leader_style", "style:leader-style"), 374 PropDef("leader_text", "style:leader-text"), 375 PropDef("style_position", "style:position"), 376 PropDef("leader_text", "style:position"), 377 ) 378 379 def __init__( # noqa: C901 380 self, 381 family: str | None = None, 382 name: str | None = None, 383 display_name: str | None = None, 384 parent_style: str | None = None, 385 # Where properties apply 386 area: str | None = None, 387 # For family 'text': 388 color: str | tuple | None = None, 389 background_color: str | tuple | None = None, 390 italic: bool = False, 391 bold: bool = False, 392 # For family 'paragraph' 393 master_page: str | None = None, 394 # For family 'master-page' 395 page_layout: str | None = None, 396 next_style: str | None = None, 397 # For family 'table-cell' 398 data_style: str | None = None, # unused 399 border: str | None = None, 400 border_top: str | None = None, 401 border_right: str | None = None, 402 border_bottom: str | None = None, 403 border_left: str | None = None, 404 padding: str | None = None, 405 padding_top: str | None = None, 406 padding_bottom: str | None = None, 407 padding_left: str | None = None, 408 padding_right: str | None = None, 409 shadow: str | None = None, 410 # For family 'table-row' 411 height: str | None = None, 412 use_optimal_height: bool = False, 413 # For family 'table-column' 414 width: str | None = None, 415 break_before: str | None = None, 416 break_after: str | None = None, 417 # For family 'graphic' 418 min_height: str | None = None, 419 # For family 'font-face' 420 font_name: str | None = None, 421 font_family: str | None = None, 422 font_family_generic: str | None = None, 423 font_pitch: str = "variable", 424 # Every other property 425 **kwargs: Any, 426 ) -> None: 427 """Create a style of the given family. The name is not mandatory at this 428 point but will become required when inserting in a document as a common 429 style. 430 431 The display name is the name the user sees in an office application. 432 433 The parent_style is the name of the style this style will inherit from. 434 435 To set properties, pass them as keyword arguments. The area properties 436 apply to is optional and defaults to the family. 437 438 Arguments: 439 440 family -- 'paragraph', 'text', 'section', 'table', 'table-column', 441 'table-row', 'table-cell', 'table-page', 'chart', 442 'drawing-page', 'graphic', 'presentation', 443 'control', 'ruby', 'list', 'number', 'page-layout' 444 'font-face', or 'master-page' 445 446 name -- str 447 448 display_name -- str 449 450 parent_style -- str 451 452 area -- str 453 454 'text' Properties: 455 456 italic -- bool 457 458 bold -- bool 459 460 'paragraph' Properties: 461 462 master_page -- str 463 464 'master-page' Properties: 465 466 page_layout -- str 467 468 next_style -- str 469 470 'table-cell' Properties: 471 472 border, border_top, border_right, border_bottom, border_left -- str, 473 e.g. "0.002cm solid #000000" or 'none' 474 475 padding, padding_top, padding_right, padding_bottom, padding_left -- str, 476 e.g. "0.002cm" or 'none' 477 478 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 479 480 'table-row' Properties: 481 482 height -- str, e.g. '5cm' 483 484 use_optimal_height -- bool 485 486 'table-column' Properties: 487 488 width -- str, e.g. '5cm' 489 490 break_before -- 'page', 'column' or 'auto' 491 492 break_after -- 'page', 'column' or 'auto' 493 """ 494 self._family: str | None = None 495 tag_or_elem = kwargs.get("tag_or_elem", None) 496 if tag_or_elem is None: 497 family = to_str(family) 498 if family not in FAMILY_MAPPING: 499 raise ValueError("Unknown family value: %s" % family) 500 kwargs["tag"] = FAMILY_MAPPING[family] 501 super().__init__(**kwargs) 502 if self._do_init and family not in SUBCLASSED_STYLES: 503 kwargs.pop("tag", None) 504 kwargs.pop("tag_or_elem", None) 505 self.family = family # relevant test made by property 506 # Common attributes 507 if name: 508 self.name = name 509 if display_name: 510 self.display_name = display_name 511 if parent_style: 512 self.parent_style = parent_style 513 # Paragraph 514 if family == "paragraph": 515 if master_page: 516 self.master_page = master_page 517 # Master Page 518 elif family == "master-page": 519 if page_layout: 520 self.page_layout = page_layout 521 if next_style: 522 self.next_style = next_style 523 # Font face 524 elif family == "font-face": 525 if not font_name: 526 raise ValueError("A font_name is required for 'font-face' style") 527 self.set_font( 528 font_name, 529 family=font_family, 530 family_generic=font_family_generic, 531 pitch=font_pitch, 532 ) 533 # Properties 534 if area is None: 535 area = family 536 area = to_str(area) 537 # Text 538 if area == "text": 539 if color: 540 kwargs["fo:color"] = color 541 if background_color: 542 kwargs["fo:background-color"] = background_color 543 if italic: 544 kwargs["fo:font-style"] = "italic" 545 kwargs["style:font-style-asian"] = "italic" 546 kwargs["style:font-style-complex"] = "italic" 547 if bold: 548 kwargs["fo:font-weight"] = "bold" 549 kwargs["style:font-weight-asian"] = "bold" 550 kwargs["style:font-weight-complex"] = "bold" 551 # Table cell 552 elif area == "table-cell": 553 if border: 554 kwargs["fo:border"] = border 555 elif border_top or border_right or border_bottom or border_left: 556 kwargs["fo:border-top"] = border_top or "none" 557 kwargs["fo:border-right"] = border_right or "none" 558 kwargs["fo:border-bottom"] = border_bottom or "none" 559 kwargs["fo:border-left"] = border_left or "none" 560 else: # no border_top, ... neither border are defined 561 pass # left untouched 562 if padding: 563 kwargs["fo:padding"] = padding 564 elif padding_top or padding_right or padding_bottom or padding_left: 565 kwargs["fo:padding-top"] = padding_top or "none" 566 kwargs["fo:padding-right"] = padding_right or "none" 567 kwargs["fo:padding-bottom"] = padding_bottom or "none" 568 kwargs["fo:padding-left"] = padding_left or "none" 569 else: # no border_top, ... neither border are defined 570 pass # left untouched 571 if shadow: 572 kwargs["style:shadow"] = shadow 573 if background_color: 574 kwargs["fo:background-color"] = background_color 575 # Table row 576 elif area == "table-row": 577 if height: 578 kwargs["style:row-height"] = height 579 if use_optimal_height: 580 kwargs["style:use-optimal-row-height"] = Boolean.encode( 581 use_optimal_height 582 ) 583 if background_color: 584 kwargs["fo:background-color"] = background_color 585 # Table column 586 elif area == "table-column": 587 if width: 588 kwargs["style:column-width"] = width 589 if break_before: 590 kwargs["fo:break-before"] = break_before 591 if break_after: 592 kwargs["fo:break-after"] = break_after 593 # Graphic 594 elif area == "graphic": 595 if min_height: 596 kwargs["fo:min-height"] = min_height 597 # Every other properties 598 if kwargs: 599 self.set_properties(kwargs, area=area) 600 601 @property 602 def family(self) -> str | None: 603 if self._family is None: 604 self._family = FALSE_FAMILY_MAP_REVERSE.get( 605 self.tag, self.get_attribute_string("style:family") 606 ) 607 return self._family 608 609 @family.setter 610 def family(self, family: str | None) -> None: 611 self._family = family 612 if family in FAMILY_ODF_STD and self.tag == "style:style": 613 self.set_attribute("style:family", family) 614 615 def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None: 616 """Get the mapping of all properties of this style. By default the 617 properties of the same family, e.g. a paragraph style and its 618 paragraph properties. Specify the area to get the text properties of 619 a paragraph style for example. 620 621 Arguments: 622 623 area -- str 624 625 Return: dict 626 """ 627 if area is None: 628 area = self.family 629 element = self.get_element(f"style:{area}-properties") 630 if element is None: 631 return None 632 properties: dict[str, str | dict] = element.attributes # type: ignore 633 # Nested properties are nested dictionaries 634 for child in element.children: 635 properties[child.tag] = child.attributes 636 return properties 637 638 def set_properties( # noqa: C901 639 self, 640 properties: dict[str, str | dict] | None = None, 641 style: Style | None = None, 642 area: str | None = None, 643 **kwargs: Any, 644 ) -> None: 645 """Set the properties of the "area" type of this style. Properties 646 are given either as a dict or as named arguments (or both). The area 647 is identical to the style family by default. If the properties 648 element is missing, it is created. 649 650 Instead of properties, you can pass a style with properties of the 651 same area. These will be copied. 652 653 Arguments: 654 655 properties -- dict 656 657 style -- Style 658 659 area -- 'paragraph', 'text'... 660 """ 661 if properties is None: 662 properties = {} 663 if area is None: 664 if isinstance(self.family, bool): 665 area = None 666 else: 667 area = self.family 668 element = self.get_element(f"style:{area}-properties") 669 if element is None: 670 element = Element.from_tag(f"style:{area}-properties") 671 self.append(element) 672 if properties or kwargs: 673 properties = _expand_properties_dict(_merge_dicts(properties, kwargs)) 674 elif style is not None: 675 properties = style.get_properties(area=area) 676 if properties is None: 677 return 678 if properties is None: 679 return 680 for key, value in properties.items(): 681 if value is None: 682 element.del_attribute(key) 683 elif isinstance(value, (str, bool)): 684 element.set_attribute(key, value) 685 else: 686 pass 687 688 def del_properties( 689 self, 690 properties: list[str] | None = None, 691 area: str | None = None, 692 ) -> None: 693 """Delete the given properties, either by list argument or 694 positional argument (or both). Remove only from the given area, 695 identical to the style family by default. 696 697 Arguments: 698 699 properties -- list 700 701 area -- str 702 """ 703 if properties is None: 704 properties = [] 705 if area is None: 706 area = self.family 707 element = self.get_element(f"style:{area}-properties") 708 if element is None: 709 raise ValueError( 710 f"properties element is inexistent for: style:{area}-properties" 711 ) 712 for key in _expand_properties_list(properties): 713 element.del_attribute(key) 714 715 def set_background( # noqa: C901 716 self, 717 color: str | None = None, 718 url: str | None = None, 719 position: str | None = "center", 720 repeat: str | None = None, 721 opacity: str | None = None, 722 filter: str | None = None, # noqa: A002 723 ) -> None: 724 """Set the background color of a text style, or the background color 725 or image of a paragraph style or page layout. 726 727 With no argument, remove any existing background. 728 729 The position is one or two of 'center', 'left', 'right', 'top' or 730 'bottom'. 731 732 The repeat is 'no-repeat', 'repeat' or 'stretch'. 733 734 The opacity is a percentage integer (not a string with the '%s' sign) 735 736 The filter is an application-specific filter name defined elsewhere. 737 738 Though this method is defined on the base style class, it will raise 739 an error if the style type is not compatible. 740 741 Arguments: 742 743 color -- '#rrggbb' 744 745 url -- str 746 747 position -- str 748 749 repeat -- str 750 751 opacity -- int 752 753 filter -- str 754 """ 755 family = self.family 756 if family not in { 757 "text", 758 "paragraph", 759 "page-layout", 760 "section", 761 "table", 762 "table-row", 763 "table-cell", 764 "graphic", 765 }: 766 raise TypeError("No background support for this family") 767 if url is not None and family == "text": 768 raise TypeError("No background image for text styles") 769 properties = self.get_element(f"style:{family}-properties") 770 bg_image: BackgroundImage | None = None 771 if properties is not None: 772 bg_image = properties.get_element("style:background-image") # type:ignore 773 # Erasing 774 if color is None and url is None: 775 if properties is None: 776 return 777 properties.del_attribute("fo:background-color") 778 if bg_image is not None: 779 properties.delete(bg_image) 780 return 781 # Add the properties if necessary 782 if properties is None: 783 properties = Element.from_tag(f"style:{family}-properties") 784 self.append(properties) 785 # Add the color... 786 if color: 787 properties.set_attribute("fo:background-color", color) 788 if bg_image is not None: 789 properties.delete(bg_image) 790 # ... or the background 791 elif url: 792 properties.set_attribute("fo:background-color", "transparent") 793 if bg_image is None: 794 bg_image = Element.from_tag("style:background-image") # type:ignore 795 properties.append(bg_image) # type:ignore 796 bg_image.url = url # type:ignore 797 if position: 798 bg_image.position = position # type:ignore 799 if repeat: 800 bg_image.repeat = repeat # type:ignore 801 if opacity: 802 bg_image.opacity = opacity # type:ignore 803 if filter: 804 bg_image.filter = filter # type:ignore 805 806 # list-style only: 807 808 def get_level_style(self, level: int) -> Style | None: 809 if self.family != "list": 810 return None 811 level_styles = ( 812 "(text:list-level-style-number" 813 "|text:list-level-style-bullet" 814 "|text:list-level-style-image)" 815 ) 816 return self._filtered_element(level_styles, 0, level=level) # type: ignore 817 818 def set_level_style( # noqa: C901 819 self, 820 level: int, 821 num_format: str | None = None, 822 bullet_char: str | None = None, 823 url: str | None = None, 824 display_levels: int | None = None, 825 prefix: str | None = None, 826 suffix: str | None = None, 827 start_value: int | None = None, 828 style: str | None = None, 829 clone: Style | None = None, 830 ) -> Style | None: 831 """ 832 Arguments: 833 834 level -- int 835 836 num_format (for number) -- int 837 838 bullet_char (for bullet) -- str 839 840 url (for image) -- str 841 842 display_levels -- int 843 844 prefix -- str 845 846 suffix -- str 847 848 start_value -- int 849 850 style -- str 851 852 clone -- List Style 853 854 Return: 855 level_style created 856 """ 857 if self.family != "list": 858 return None 859 # Expected name 860 if num_format is not None: 861 level_style_name = "text:list-level-style-number" 862 elif bullet_char is not None: 863 level_style_name = "text:list-level-style-bullet" 864 elif url is not None: 865 level_style_name = "text:list-level-style-image" 866 elif clone is not None: 867 level_style_name = clone.tag 868 else: 869 raise ValueError("unknown level style type") 870 was_created = False 871 # Cloning or reusing an existing element 872 level_style: Style | None = None 873 if clone is not None: 874 level_style = clone.clone # type: ignore 875 was_created = True 876 else: 877 level_style = self.get_level_style(level) 878 if level_style is None: 879 level_style = Element.from_tag(level_style_name) # type: ignore 880 was_created = True 881 if level_style is None: 882 return None 883 # Transmute if the type changed 884 if level_style.tag != level_style_name: 885 print("Warn: different style", level_style_name, level_style.tag) 886 level_style.tag = level_style_name 887 # Set the level 888 level_style.set_attribute("text:level", str(level)) 889 # Set the main attribute 890 if num_format is not None: 891 level_style.set_attribute("fo:num-format", num_format) 892 elif bullet_char is not None: 893 level_style.set_attribute("text:bullet-char", bullet_char) 894 elif url is not None: 895 level_style.set_attribute("xlink:href", url) 896 # Set attributes 897 if prefix: 898 level_style.set_attribute("style:num-prefix", prefix) 899 if suffix: 900 level_style.set_attribute("style:num-suffix", suffix) 901 if display_levels: 902 level_style.set_attribute("text:display-levels", str(display_levels)) 903 if start_value: 904 level_style.set_attribute("text:start-value", str(start_value)) 905 if style: 906 level_style.text_style = style # type: ignore 907 # Commit the creation 908 if was_created: 909 self.append(level_style) 910 return level_style 911 912 # page-layout only: 913 914 def get_header_style(self) -> Element | None: 915 if self.family != "page-layout": 916 return None 917 return self.get_element("style:header-style") 918 919 def set_header_style(self, new_style: Style) -> None: 920 if self.family != "page-layout": 921 return 922 header_style = self.get_header_style() 923 if header_style is not None: 924 self.delete(header_style) 925 self.append(new_style) 926 927 def get_footer_style(self) -> Style | None: 928 if self.family != "page-layout": 929 return None 930 return self.get_element("style:footer-style") # type: ignore 931 932 def set_footer_style(self, new_style: Style) -> None: 933 if self.family != "page-layout": 934 return 935 footer_style = self.get_footer_style() 936 if footer_style is not None: 937 self.delete(footer_style) 938 self.append(new_style) 939 940 # master-page only: 941 942 def _set_header_or_footer( 943 self, 944 text_or_element: str | Element | list[Element | str], 945 name: str = "header", 946 style: str = "Header", 947 ) -> None: 948 if name == "header": 949 header_or_footer = self.get_page_header() 950 else: 951 header_or_footer = self.get_page_footer() 952 if header_or_footer is None: 953 header_or_footer = Element.from_tag("style:" + name) 954 self.append(header_or_footer) 955 else: 956 header_or_footer.clear() 957 if ( 958 isinstance(text_or_element, Element) 959 and text_or_element.tag == f"style:{name}" 960 ): 961 # Already a header or footer? 962 self.delete(header_or_footer) 963 self.append(text_or_element) 964 return 965 if isinstance(text_or_element, (Element, str)): 966 elem_list: list[Element | str] = [text_or_element] 967 else: 968 elem_list = text_or_element 969 for item in elem_list: 970 if isinstance(item, str): 971 paragraph = Element.from_tag("text:p") 972 paragraph.style = style # type: ignore 973 header_or_footer.append(paragraph) 974 elif isinstance(item, Element): 975 header_or_footer.append(item) 976 977 def get_page_header(self) -> Element | None: 978 """Get the element that contains the header contents. 979 980 If None, no header was set. 981 """ 982 if self.family != "master-page": 983 return None 984 return self.get_element("style:header") 985 986 def set_page_header( 987 self, 988 text_or_element: str | Element | list[Element | str], 989 ) -> None: 990 """Create or replace the header by the given content. It can already 991 be a complete header. 992 993 If you only want to update the existing header, get it and use the 994 API. 995 996 Arguments: 997 998 text_or_element -- str or Element or a list of them 999 """ 1000 if self.family != "master-page": 1001 return None 1002 self._set_header_or_footer(text_or_element) 1003 1004 def get_page_footer(self) -> Element | None: 1005 """Get the element that contains the footer contents. 1006 1007 If None, no footer was set. 1008 """ 1009 if self.family != "master-page": 1010 return None 1011 return self.get_element("style:footer") 1012 1013 def set_page_footer( 1014 self, 1015 text_or_element: str | Element | list[Element | str], 1016 ) -> None: 1017 """Create or replace the footer by the given content. It can already 1018 be a complete footer. 1019 1020 If you only want to update the existing footer, get it and use the 1021 API. 1022 1023 Arguments: 1024 1025 text_or_element -- str or Element or a list of them 1026 """ 1027 if self.family != "master-page": 1028 return None 1029 self._set_header_or_footer(text_or_element, name="footer", style="Footer") 1030 1031 # font-face only: 1032 1033 def set_font( 1034 self, 1035 name: str, 1036 family: str | None = None, 1037 family_generic: str | None = None, 1038 pitch: str = "variable", 1039 ) -> None: 1040 if self.family != "font-face": 1041 return 1042 self.name = name 1043 if family is None: 1044 family = name 1045 self.svg_font_family = f'"{family}"' 1046 if family_generic is not None: 1047 self.font_family_generic = family_generic 1048 self.font_pitch = pitch
Style class for all these tags:
'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...
379 def __init__( # noqa: C901 380 self, 381 family: str | None = None, 382 name: str | None = None, 383 display_name: str | None = None, 384 parent_style: str | None = None, 385 # Where properties apply 386 area: str | None = None, 387 # For family 'text': 388 color: str | tuple | None = None, 389 background_color: str | tuple | None = None, 390 italic: bool = False, 391 bold: bool = False, 392 # For family 'paragraph' 393 master_page: str | None = None, 394 # For family 'master-page' 395 page_layout: str | None = None, 396 next_style: str | None = None, 397 # For family 'table-cell' 398 data_style: str | None = None, # unused 399 border: str | None = None, 400 border_top: str | None = None, 401 border_right: str | None = None, 402 border_bottom: str | None = None, 403 border_left: str | None = None, 404 padding: str | None = None, 405 padding_top: str | None = None, 406 padding_bottom: str | None = None, 407 padding_left: str | None = None, 408 padding_right: str | None = None, 409 shadow: str | None = None, 410 # For family 'table-row' 411 height: str | None = None, 412 use_optimal_height: bool = False, 413 # For family 'table-column' 414 width: str | None = None, 415 break_before: str | None = None, 416 break_after: str | None = None, 417 # For family 'graphic' 418 min_height: str | None = None, 419 # For family 'font-face' 420 font_name: str | None = None, 421 font_family: str | None = None, 422 font_family_generic: str | None = None, 423 font_pitch: str = "variable", 424 # Every other property 425 **kwargs: Any, 426 ) -> None: 427 """Create a style of the given family. The name is not mandatory at this 428 point but will become required when inserting in a document as a common 429 style. 430 431 The display name is the name the user sees in an office application. 432 433 The parent_style is the name of the style this style will inherit from. 434 435 To set properties, pass them as keyword arguments. The area properties 436 apply to is optional and defaults to the family. 437 438 Arguments: 439 440 family -- 'paragraph', 'text', 'section', 'table', 'table-column', 441 'table-row', 'table-cell', 'table-page', 'chart', 442 'drawing-page', 'graphic', 'presentation', 443 'control', 'ruby', 'list', 'number', 'page-layout' 444 'font-face', or 'master-page' 445 446 name -- str 447 448 display_name -- str 449 450 parent_style -- str 451 452 area -- str 453 454 'text' Properties: 455 456 italic -- bool 457 458 bold -- bool 459 460 'paragraph' Properties: 461 462 master_page -- str 463 464 'master-page' Properties: 465 466 page_layout -- str 467 468 next_style -- str 469 470 'table-cell' Properties: 471 472 border, border_top, border_right, border_bottom, border_left -- str, 473 e.g. "0.002cm solid #000000" or 'none' 474 475 padding, padding_top, padding_right, padding_bottom, padding_left -- str, 476 e.g. "0.002cm" or 'none' 477 478 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 479 480 'table-row' Properties: 481 482 height -- str, e.g. '5cm' 483 484 use_optimal_height -- bool 485 486 'table-column' Properties: 487 488 width -- str, e.g. '5cm' 489 490 break_before -- 'page', 'column' or 'auto' 491 492 break_after -- 'page', 'column' or 'auto' 493 """ 494 self._family: str | None = None 495 tag_or_elem = kwargs.get("tag_or_elem", None) 496 if tag_or_elem is None: 497 family = to_str(family) 498 if family not in FAMILY_MAPPING: 499 raise ValueError("Unknown family value: %s" % family) 500 kwargs["tag"] = FAMILY_MAPPING[family] 501 super().__init__(**kwargs) 502 if self._do_init and family not in SUBCLASSED_STYLES: 503 kwargs.pop("tag", None) 504 kwargs.pop("tag_or_elem", None) 505 self.family = family # relevant test made by property 506 # Common attributes 507 if name: 508 self.name = name 509 if display_name: 510 self.display_name = display_name 511 if parent_style: 512 self.parent_style = parent_style 513 # Paragraph 514 if family == "paragraph": 515 if master_page: 516 self.master_page = master_page 517 # Master Page 518 elif family == "master-page": 519 if page_layout: 520 self.page_layout = page_layout 521 if next_style: 522 self.next_style = next_style 523 # Font face 524 elif family == "font-face": 525 if not font_name: 526 raise ValueError("A font_name is required for 'font-face' style") 527 self.set_font( 528 font_name, 529 family=font_family, 530 family_generic=font_family_generic, 531 pitch=font_pitch, 532 ) 533 # Properties 534 if area is None: 535 area = family 536 area = to_str(area) 537 # Text 538 if area == "text": 539 if color: 540 kwargs["fo:color"] = color 541 if background_color: 542 kwargs["fo:background-color"] = background_color 543 if italic: 544 kwargs["fo:font-style"] = "italic" 545 kwargs["style:font-style-asian"] = "italic" 546 kwargs["style:font-style-complex"] = "italic" 547 if bold: 548 kwargs["fo:font-weight"] = "bold" 549 kwargs["style:font-weight-asian"] = "bold" 550 kwargs["style:font-weight-complex"] = "bold" 551 # Table cell 552 elif area == "table-cell": 553 if border: 554 kwargs["fo:border"] = border 555 elif border_top or border_right or border_bottom or border_left: 556 kwargs["fo:border-top"] = border_top or "none" 557 kwargs["fo:border-right"] = border_right or "none" 558 kwargs["fo:border-bottom"] = border_bottom or "none" 559 kwargs["fo:border-left"] = border_left or "none" 560 else: # no border_top, ... neither border are defined 561 pass # left untouched 562 if padding: 563 kwargs["fo:padding"] = padding 564 elif padding_top or padding_right or padding_bottom or padding_left: 565 kwargs["fo:padding-top"] = padding_top or "none" 566 kwargs["fo:padding-right"] = padding_right or "none" 567 kwargs["fo:padding-bottom"] = padding_bottom or "none" 568 kwargs["fo:padding-left"] = padding_left or "none" 569 else: # no border_top, ... neither border are defined 570 pass # left untouched 571 if shadow: 572 kwargs["style:shadow"] = shadow 573 if background_color: 574 kwargs["fo:background-color"] = background_color 575 # Table row 576 elif area == "table-row": 577 if height: 578 kwargs["style:row-height"] = height 579 if use_optimal_height: 580 kwargs["style:use-optimal-row-height"] = Boolean.encode( 581 use_optimal_height 582 ) 583 if background_color: 584 kwargs["fo:background-color"] = background_color 585 # Table column 586 elif area == "table-column": 587 if width: 588 kwargs["style:column-width"] = width 589 if break_before: 590 kwargs["fo:break-before"] = break_before 591 if break_after: 592 kwargs["fo:break-after"] = break_after 593 # Graphic 594 elif area == "graphic": 595 if min_height: 596 kwargs["fo:min-height"] = min_height 597 # Every other properties 598 if kwargs: 599 self.set_properties(kwargs, area=area)
Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.
The display name is the name the user sees in an office application.
The parent_style is the name of the style this style will inherit from.
To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.
Arguments:
family -- 'paragraph', 'text', 'section', 'table', 'table-column',
'table-row', 'table-cell', 'table-page', 'chart',
'drawing-page', 'graphic', 'presentation',
'control', 'ruby', 'list', 'number', 'page-layout'
'font-face', or 'master-page'
name -- str
display_name -- str
parent_style -- str
area -- str
'text' Properties:
italic -- bool
bold -- bool
'paragraph' Properties:
master_page -- str
'master-page' Properties:
page_layout -- str
next_style -- str
'table-cell' Properties:
border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'
padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
'table-row' Properties:
height -- str, e.g. '5cm'
use_optimal_height -- bool
'table-column' Properties:
width -- str, e.g. '5cm'
break_before -- 'page', 'column' or 'auto'
break_after -- 'page', 'column' or 'auto'
615 def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None: 616 """Get the mapping of all properties of this style. By default the 617 properties of the same family, e.g. a paragraph style and its 618 paragraph properties. Specify the area to get the text properties of 619 a paragraph style for example. 620 621 Arguments: 622 623 area -- str 624 625 Return: dict 626 """ 627 if area is None: 628 area = self.family 629 element = self.get_element(f"style:{area}-properties") 630 if element is None: 631 return None 632 properties: dict[str, str | dict] = element.attributes # type: ignore 633 # Nested properties are nested dictionaries 634 for child in element.children: 635 properties[child.tag] = child.attributes 636 return properties
Get the mapping of all properties of this style. By default the properties of the same family, e.g. a paragraph style and its paragraph properties. Specify the area to get the text properties of a paragraph style for example.
Arguments:
area -- str
Return: dict
638 def set_properties( # noqa: C901 639 self, 640 properties: dict[str, str | dict] | None = None, 641 style: Style | None = None, 642 area: str | None = None, 643 **kwargs: Any, 644 ) -> None: 645 """Set the properties of the "area" type of this style. Properties 646 are given either as a dict or as named arguments (or both). The area 647 is identical to the style family by default. If the properties 648 element is missing, it is created. 649 650 Instead of properties, you can pass a style with properties of the 651 same area. These will be copied. 652 653 Arguments: 654 655 properties -- dict 656 657 style -- Style 658 659 area -- 'paragraph', 'text'... 660 """ 661 if properties is None: 662 properties = {} 663 if area is None: 664 if isinstance(self.family, bool): 665 area = None 666 else: 667 area = self.family 668 element = self.get_element(f"style:{area}-properties") 669 if element is None: 670 element = Element.from_tag(f"style:{area}-properties") 671 self.append(element) 672 if properties or kwargs: 673 properties = _expand_properties_dict(_merge_dicts(properties, kwargs)) 674 elif style is not None: 675 properties = style.get_properties(area=area) 676 if properties is None: 677 return 678 if properties is None: 679 return 680 for key, value in properties.items(): 681 if value is None: 682 element.del_attribute(key) 683 elif isinstance(value, (str, bool)): 684 element.set_attribute(key, value) 685 else: 686 pass
Set the properties of the "area" type of this style. Properties are given either as a dict or as named arguments (or both). The area is identical to the style family by default. If the properties element is missing, it is created.
Instead of properties, you can pass a style with properties of the same area. These will be copied.
Arguments:
properties -- dict
style -- Style
area -- 'paragraph', 'text'...
688 def del_properties( 689 self, 690 properties: list[str] | None = None, 691 area: str | None = None, 692 ) -> None: 693 """Delete the given properties, either by list argument or 694 positional argument (or both). Remove only from the given area, 695 identical to the style family by default. 696 697 Arguments: 698 699 properties -- list 700 701 area -- str 702 """ 703 if properties is None: 704 properties = [] 705 if area is None: 706 area = self.family 707 element = self.get_element(f"style:{area}-properties") 708 if element is None: 709 raise ValueError( 710 f"properties element is inexistent for: style:{area}-properties" 711 ) 712 for key in _expand_properties_list(properties): 713 element.del_attribute(key)
Delete the given properties, either by list argument or positional argument (or both). Remove only from the given area, identical to the style family by default.
Arguments:
properties -- list
area -- str
715 def set_background( # noqa: C901 716 self, 717 color: str | None = None, 718 url: str | None = None, 719 position: str | None = "center", 720 repeat: str | None = None, 721 opacity: str | None = None, 722 filter: str | None = None, # noqa: A002 723 ) -> None: 724 """Set the background color of a text style, or the background color 725 or image of a paragraph style or page layout. 726 727 With no argument, remove any existing background. 728 729 The position is one or two of 'center', 'left', 'right', 'top' or 730 'bottom'. 731 732 The repeat is 'no-repeat', 'repeat' or 'stretch'. 733 734 The opacity is a percentage integer (not a string with the '%s' sign) 735 736 The filter is an application-specific filter name defined elsewhere. 737 738 Though this method is defined on the base style class, it will raise 739 an error if the style type is not compatible. 740 741 Arguments: 742 743 color -- '#rrggbb' 744 745 url -- str 746 747 position -- str 748 749 repeat -- str 750 751 opacity -- int 752 753 filter -- str 754 """ 755 family = self.family 756 if family not in { 757 "text", 758 "paragraph", 759 "page-layout", 760 "section", 761 "table", 762 "table-row", 763 "table-cell", 764 "graphic", 765 }: 766 raise TypeError("No background support for this family") 767 if url is not None and family == "text": 768 raise TypeError("No background image for text styles") 769 properties = self.get_element(f"style:{family}-properties") 770 bg_image: BackgroundImage | None = None 771 if properties is not None: 772 bg_image = properties.get_element("style:background-image") # type:ignore 773 # Erasing 774 if color is None and url is None: 775 if properties is None: 776 return 777 properties.del_attribute("fo:background-color") 778 if bg_image is not None: 779 properties.delete(bg_image) 780 return 781 # Add the properties if necessary 782 if properties is None: 783 properties = Element.from_tag(f"style:{family}-properties") 784 self.append(properties) 785 # Add the color... 786 if color: 787 properties.set_attribute("fo:background-color", color) 788 if bg_image is not None: 789 properties.delete(bg_image) 790 # ... or the background 791 elif url: 792 properties.set_attribute("fo:background-color", "transparent") 793 if bg_image is None: 794 bg_image = Element.from_tag("style:background-image") # type:ignore 795 properties.append(bg_image) # type:ignore 796 bg_image.url = url # type:ignore 797 if position: 798 bg_image.position = position # type:ignore 799 if repeat: 800 bg_image.repeat = repeat # type:ignore 801 if opacity: 802 bg_image.opacity = opacity # type:ignore 803 if filter: 804 bg_image.filter = filter # type:ignore
Set the background color of a text style, or the background color or image of a paragraph style or page layout.
With no argument, remove any existing background.
The position is one or two of 'center', 'left', 'right', 'top' or 'bottom'.
The repeat is 'no-repeat', 'repeat' or 'stretch'.
The opacity is a percentage integer (not a string with the '%s' sign)
The filter is an application-specific filter name defined elsewhere.
Though this method is defined on the base style class, it will raise an error if the style type is not compatible.
Arguments:
color -- '#rrggbb'
url -- str
position -- str
repeat -- str
opacity -- int
filter -- str
808 def get_level_style(self, level: int) -> Style | None: 809 if self.family != "list": 810 return None 811 level_styles = ( 812 "(text:list-level-style-number" 813 "|text:list-level-style-bullet" 814 "|text:list-level-style-image)" 815 ) 816 return self._filtered_element(level_styles, 0, level=level) # type: ignore
818 def set_level_style( # noqa: C901 819 self, 820 level: int, 821 num_format: str | None = None, 822 bullet_char: str | None = None, 823 url: str | None = None, 824 display_levels: int | None = None, 825 prefix: str | None = None, 826 suffix: str | None = None, 827 start_value: int | None = None, 828 style: str | None = None, 829 clone: Style | None = None, 830 ) -> Style | None: 831 """ 832 Arguments: 833 834 level -- int 835 836 num_format (for number) -- int 837 838 bullet_char (for bullet) -- str 839 840 url (for image) -- str 841 842 display_levels -- int 843 844 prefix -- str 845 846 suffix -- str 847 848 start_value -- int 849 850 style -- str 851 852 clone -- List Style 853 854 Return: 855 level_style created 856 """ 857 if self.family != "list": 858 return None 859 # Expected name 860 if num_format is not None: 861 level_style_name = "text:list-level-style-number" 862 elif bullet_char is not None: 863 level_style_name = "text:list-level-style-bullet" 864 elif url is not None: 865 level_style_name = "text:list-level-style-image" 866 elif clone is not None: 867 level_style_name = clone.tag 868 else: 869 raise ValueError("unknown level style type") 870 was_created = False 871 # Cloning or reusing an existing element 872 level_style: Style | None = None 873 if clone is not None: 874 level_style = clone.clone # type: ignore 875 was_created = True 876 else: 877 level_style = self.get_level_style(level) 878 if level_style is None: 879 level_style = Element.from_tag(level_style_name) # type: ignore 880 was_created = True 881 if level_style is None: 882 return None 883 # Transmute if the type changed 884 if level_style.tag != level_style_name: 885 print("Warn: different style", level_style_name, level_style.tag) 886 level_style.tag = level_style_name 887 # Set the level 888 level_style.set_attribute("text:level", str(level)) 889 # Set the main attribute 890 if num_format is not None: 891 level_style.set_attribute("fo:num-format", num_format) 892 elif bullet_char is not None: 893 level_style.set_attribute("text:bullet-char", bullet_char) 894 elif url is not None: 895 level_style.set_attribute("xlink:href", url) 896 # Set attributes 897 if prefix: 898 level_style.set_attribute("style:num-prefix", prefix) 899 if suffix: 900 level_style.set_attribute("style:num-suffix", suffix) 901 if display_levels: 902 level_style.set_attribute("text:display-levels", str(display_levels)) 903 if start_value: 904 level_style.set_attribute("text:start-value", str(start_value)) 905 if style: 906 level_style.text_style = style # type: ignore 907 # Commit the creation 908 if was_created: 909 self.append(level_style) 910 return level_style
Arguments:
level -- int
num_format (for number) -- int
bullet_char (for bullet) -- str
url (for image) -- str
display_levels -- int
prefix -- str
suffix -- str
start_value -- int
style -- str
clone -- List Style
Return: level_style created
977 def get_page_header(self) -> Element | None: 978 """Get the element that contains the header contents. 979 980 If None, no header was set. 981 """ 982 if self.family != "master-page": 983 return None 984 return self.get_element("style:header")
Get the element that contains the header contents.
If None, no header was set.
986 def set_page_header( 987 self, 988 text_or_element: str | Element | list[Element | str], 989 ) -> None: 990 """Create or replace the header by the given content. It can already 991 be a complete header. 992 993 If you only want to update the existing header, get it and use the 994 API. 995 996 Arguments: 997 998 text_or_element -- str or Element or a list of them 999 """ 1000 if self.family != "master-page": 1001 return None 1002 self._set_header_or_footer(text_or_element)
Create or replace the header by the given content. It can already be a complete header.
If you only want to update the existing header, get it and use the API.
Arguments:
text_or_element -- str or Element or a list of them
1033 def set_font( 1034 self, 1035 name: str, 1036 family: str | None = None, 1037 family_generic: str | None = None, 1038 pitch: str = "variable", 1039 ) -> None: 1040 if self.family != "font-face": 1041 return 1042 self.name = name 1043 if family is None: 1044 family = name 1045 self.svg_font_family = f'"{family}"' 1046 if family_generic is not None: 1047 self.font_family_generic = family_generic 1048 self.font_pitch = pitch
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
58class Styles(XmlPart): 59 def _get_style_contexts( 60 self, family: str, automatic: bool = False 61 ) -> list[Element]: 62 if automatic: 63 return [self.get_element("//office:automatic-styles")] 64 if not family: 65 # All possibilities 66 return [ 67 self.get_element("//office:automatic-styles"), 68 self.get_element("//office:styles"), 69 self.get_element("//office:master-styles"), 70 self.get_element("//office:font-face-decls"), 71 ] 72 queries = CONTEXT_MAPPING.get(family) 73 if queries is None: 74 raise ValueError(f"unknown family: {family}") 75 # print('q:', queries) 76 return [self.get_element(query) for query in queries] 77 78 def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]: 79 """Return the list of styles in the Content part, optionally limited 80 to the given family, optionaly limited to automatic styles. 81 82 Arguments: 83 84 family -- str 85 86 automatic -- bool 87 88 Return: list of Style 89 """ 90 result = [] 91 for context in self._get_style_contexts(family, automatic=automatic): 92 if context is None: 93 continue 94 # print('-ctx----', automatic) 95 # print(context.tag) 96 # print(context.__class__) 97 # print(context.serialize()) 98 result.extend(context.get_styles(family=family)) 99 return result 100 101 def get_style( 102 self, 103 family: str, 104 name_or_element: str | Style | None = None, 105 display_name: str | None = None, 106 ) -> Style | None: 107 """Return the style uniquely identified by the name/family pair. If 108 the argument is already a style object, it will return it. 109 110 If the name is None, the default style is fetched. 111 112 If the name is not the internal name but the name you gave in the 113 desktop application, use display_name instead. 114 115 Arguments: 116 117 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 118 'number', 'page-layout', 'master-page' 119 120 name_or_element -- str, odf_style or None 121 122 display_name -- str or None 123 124 Return: odf_style or None if not found 125 """ 126 for context in self._get_style_contexts(family): 127 if context is None: 128 continue 129 style = context.get_style( 130 family, 131 name_or_element=name_or_element, 132 display_name=display_name, 133 ) 134 if style is not None: 135 return style # type: ignore 136 return None 137 138 def get_master_pages(self) -> list[Element]: 139 query = make_xpath_query("descendant::style:master-page") 140 return self.get_elements(query) # type:ignore 141 142 def get_master_page(self, position: int = 0) -> Element | None: 143 results = self.get_master_pages() 144 try: 145 return results[position] 146 except IndexError: 147 return None
Representation of an XML part.
Abstraction of the XML library behind.
78 def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]: 79 """Return the list of styles in the Content part, optionally limited 80 to the given family, optionaly limited to automatic styles. 81 82 Arguments: 83 84 family -- str 85 86 automatic -- bool 87 88 Return: list of Style 89 """ 90 result = [] 91 for context in self._get_style_contexts(family, automatic=automatic): 92 if context is None: 93 continue 94 # print('-ctx----', automatic) 95 # print(context.tag) 96 # print(context.__class__) 97 # print(context.serialize()) 98 result.extend(context.get_styles(family=family)) 99 return result
Return the list of styles in the Content part, optionally limited to the given family, optionaly limited to automatic styles.
Arguments:
family -- str
automatic -- bool
Return: list of Style
101 def get_style( 102 self, 103 family: str, 104 name_or_element: str | Style | None = None, 105 display_name: str | None = None, 106 ) -> Style | None: 107 """Return the style uniquely identified by the name/family pair. If 108 the argument is already a style object, it will return it. 109 110 If the name is None, the default style is fetched. 111 112 If the name is not the internal name but the name you gave in the 113 desktop application, use display_name instead. 114 115 Arguments: 116 117 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 118 'number', 'page-layout', 'master-page' 119 120 name_or_element -- str, odf_style or None 121 122 display_name -- str or None 123 124 Return: odf_style or None if not found 125 """ 126 for context in self._get_style_contexts(family): 127 if context is None: 128 continue 129 style = context.get_style( 130 family, 131 name_or_element=name_or_element, 132 display_name=display_name, 133 ) 134 if style is not None: 135 return style # type: ignore 136 return None
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number', 'page-layout', 'master-page'
name_or_element -- str, odf_style or None
display_name -- str or None
Return: odf_style or None if not found
Inherited Members
168class TOC(Element): 169 """Table of content. 170 The "text:table-of-content" element represents a table of contents for a 171 document. The items that can be listed in a table of contents are: 172 - Headings (as defined by the outline structure of the document), up to 173 a selected level. 174 - Table of contents index marks. 175 - Paragraphs formatted with specified paragraph styles. 176 177 178 Implementation: 179 Default parameters are what most people use: protected from manual 180 modifications and not limited in title levels. 181 182 The name is mandatory and derived automatically from the title if not 183 given. Provide one in case of a conflict with other TOCs in the same 184 document. 185 186 The "text:table-of-content" element has the following attributes: 187 text:name, text:protected, text:protection-key, 188 text:protection-key-digest-algorithm, text:style-name and xml:id. 189 190 Arguments: 191 192 title -- str 193 194 name -- str 195 196 protected -- bool 197 198 outline_level -- int 199 200 style -- str 201 202 title_style -- str 203 204 entry_style -- str 205 """ 206 207 _tag = "text:table-of-content" 208 _properties = ( 209 PropDef("name", "text:name"), 210 PropDef("style", "text:style-name"), 211 PropDef("xml_id", "xml:id"), 212 PropDef("protected", "text:protected"), 213 PropDef("protection_key", "text:protection-key"), 214 PropDef( 215 "protection_key_digest_algorithm", "text:protection-key-digest-algorithm" 216 ), 217 ) 218 219 def __init__( 220 self, 221 title: str = "Table of Contents", 222 name: str | None = None, 223 protected: bool = True, 224 outline_level: int = 0, 225 style: str | None = None, 226 title_style: str = "Contents_20_Heading", 227 entry_style: str = "Contents_20_%d", 228 **kwargs: Any, 229 ) -> None: 230 super().__init__(**kwargs) 231 if self._do_init: 232 if style: 233 self.style = style 234 if protected: 235 self.protected = protected 236 if name is None: 237 self.name = f"{title}1" 238 # Create the source template 239 toc_source = self.create_toc_source( 240 title, outline_level, title_style, entry_style 241 ) 242 self.append(toc_source) 243 # Create the index body automatically with the index title 244 if title: 245 # This style is in the template document 246 self.set_toc_title(title, text_style=title_style) 247 248 @staticmethod 249 def create_toc_source( 250 title: str, 251 outline_level: int, 252 title_style: str, 253 entry_style: str, 254 ) -> Element: 255 toc_source = Element.from_tag("text:table-of-content-source") 256 toc_source.set_attribute("text:outline-level", str(outline_level)) 257 if title: 258 title_template = IndexTitleTemplate() 259 if title_style: 260 # This style is in the template document 261 title_template.style = title_style 262 title_template.text = title 263 toc_source.append(title_template) 264 for level in range(1, 11): 265 template = TocEntryTemplate(outline_level=level) 266 if entry_style: 267 template.style = entry_style % level 268 toc_source.append(template) 269 return toc_source 270 271 def __str__(self) -> str: 272 return self.get_formatted_text() 273 274 def get_formatted_text(self, context: dict | None = None) -> str: 275 index_body = self.get_element("text:index-body") 276 277 if index_body is None: 278 return "" 279 if context is None: 280 context = {} 281 if context.get("rst_mode"): 282 return "\n.. contents::\n\n" 283 284 result = [] 285 for element in index_body.children: 286 if element.tag == "text:index-title": 287 for child_element in element.children: 288 result.append(child_element.get_formatted_text(context).strip()) 289 else: 290 result.append(element.get_formatted_text(context).strip()) 291 return "\n".join(result) 292 293 @property 294 def outline_level(self) -> int | None: 295 source = self.get_element("text:table-of-content-source") 296 if source is None: 297 return None 298 return source.get_attribute_integer("text:outline-level") 299 300 @outline_level.setter 301 def outline_level(self, level: int) -> None: 302 source = self.get_element("text:table-of-content-source") 303 if source is None: 304 source = Element.from_tag("text:table-of-content-source") 305 self.insert(source, FIRST_CHILD) 306 source.set_attribute("text:outline-level", str(level)) 307 308 @property 309 def body(self) -> Element | None: 310 return self.get_element("text:index-body") 311 312 @body.setter 313 def body(self, body: Element | None = None) -> Element | None: 314 old_body = self.body 315 if old_body is not None: 316 self.delete(old_body) 317 if body is None: 318 body = Element.from_tag("text:index-body") 319 self.append(body) 320 return body 321 322 def get_title(self) -> str: 323 index_body = self.body 324 if index_body is None: 325 return "" 326 index_title = index_body.get_element(IndexTitle._tag) 327 if index_title is None: 328 return "" 329 return index_title.text_content 330 331 def set_toc_title( 332 self, 333 title: str, 334 style: str | None = None, 335 text_style: str | None = None, 336 ) -> None: 337 index_body = self.body 338 if index_body is None: 339 self.body = None 340 index_body = self.body 341 index_title = index_body.get_element(IndexTitle._tag) # type: ignore 342 if index_title is None: 343 name = f"{self.name}_Head" 344 index_title = IndexTitle( 345 name=name, style=style, title_text=title, text_style=text_style 346 ) 347 index_body.append(index_title) # type: ignore 348 else: 349 if style: 350 index_title.style = style # type: ignore 351 paragraph = index_title.get_paragraph() 352 if paragraph is None: 353 paragraph = Paragraph() 354 index_title.append(paragraph) 355 if text_style: 356 paragraph.style = text_style # type: ignore 357 paragraph.text = title 358 359 @staticmethod 360 def _header_numbering(level_indexes: dict[int, int], level: int) -> str: 361 """Return the header hierarchical number (like "1.2.3.").""" 362 numbers: list[int] = [] 363 # before header level 364 for idx in range(1, level): 365 numbers.append(level_indexes.setdefault(idx, 1)) 366 # header level 367 index = level_indexes.get(level, 0) + 1 368 level_indexes[level] = index 369 numbers.append(index) 370 # after header level 371 idx = level + 1 372 while idx in level_indexes: 373 del level_indexes[idx] 374 idx += 1 375 return ".".join(str(x) for x in numbers) + "." 376 377 def fill( # noqa: C901 378 self, 379 document: Document | None = None, 380 use_default_styles: bool = True, 381 ) -> None: 382 """Fill the TOC with the titles found in the document. A TOC is not 383 contextual so it will catch all titles before and after its insertion. 384 If the TOC is not attached to a document, attach it beforehand or 385 provide one as argument. 386 387 For having a pretty TOC, let use_default_styles by default. 388 389 Arguments: 390 391 document -- Document 392 393 use_default_styles -- bool 394 """ 395 # Find the body 396 if document is not None: 397 body: Element | None = document.body 398 else: 399 body = self.document_body 400 if body is None: 401 raise ValueError("The TOC must be related to a document somehow") 402 403 # Save the title 404 index_body = self.body 405 title = index_body.get_element("text:index-title") # type: ignore 406 407 # Clean the old index-body 408 self.body = None 409 index_body = self.body 410 411 # Restore the title 412 if title and str(title): 413 index_body.insert(title, position=0) # type: ignore 414 415 # Insert default TOC style 416 if use_default_styles: 417 automatic_styles = body.get_element("//office:automatic-styles") 418 if isinstance(automatic_styles, Element): 419 for level in range(1, 11): 420 if ( 421 automatic_styles.get_style( 422 "paragraph", _toc_entry_style_name(level) 423 ) 424 is None 425 ): 426 level_style = default_toc_level_style(level) 427 automatic_styles.append(level_style) 428 429 # Auto-fill the index 430 outline_level = self.outline_level or 10 431 level_indexes: dict[int, int] = {} 432 for header in body.get_headers(): 433 level = header.get_attribute_integer("text:outline-level") or 0 434 if level is None or level > outline_level: 435 continue 436 number_str = self._header_numbering(level_indexes, level) 437 # Make the title with "1.2.3. Title" format 438 paragraph = Paragraph(f"{number_str} {header}") 439 if use_default_styles: 440 paragraph.style = _toc_entry_style_name(level) 441 index_body.append(paragraph) # type: ignore
Table of content. The "text:table-of-content" element represents a table of contents for a document. The items that can be listed in a table of contents are:
- Headings (as defined by the outline structure of the document), up to a selected level.
- Table of contents index marks.
- Paragraphs formatted with specified paragraph styles.
Implementation: Default parameters are what most people use: protected from manual modifications and not limited in title levels.
The name is mandatory and derived automatically from the title if not given. Provide one in case of a conflict with other TOCs in the same document.
The "text:table-of-content" element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name and xml:id.
Arguments:
title -- str
name -- str
protected -- bool
outline_level -- int
style -- str
title_style -- str
entry_style -- str
219 def __init__( 220 self, 221 title: str = "Table of Contents", 222 name: str | None = None, 223 protected: bool = True, 224 outline_level: int = 0, 225 style: str | None = None, 226 title_style: str = "Contents_20_Heading", 227 entry_style: str = "Contents_20_%d", 228 **kwargs: Any, 229 ) -> None: 230 super().__init__(**kwargs) 231 if self._do_init: 232 if style: 233 self.style = style 234 if protected: 235 self.protected = protected 236 if name is None: 237 self.name = f"{title}1" 238 # Create the source template 239 toc_source = self.create_toc_source( 240 title, outline_level, title_style, entry_style 241 ) 242 self.append(toc_source) 243 # Create the index body automatically with the index title 244 if title: 245 # This style is in the template document 246 self.set_toc_title(title, text_style=title_style)
248 @staticmethod 249 def create_toc_source( 250 title: str, 251 outline_level: int, 252 title_style: str, 253 entry_style: str, 254 ) -> Element: 255 toc_source = Element.from_tag("text:table-of-content-source") 256 toc_source.set_attribute("text:outline-level", str(outline_level)) 257 if title: 258 title_template = IndexTitleTemplate() 259 if title_style: 260 # This style is in the template document 261 title_template.style = title_style 262 title_template.text = title 263 toc_source.append(title_template) 264 for level in range(1, 11): 265 template = TocEntryTemplate(outline_level=level) 266 if entry_style: 267 template.style = entry_style % level 268 toc_source.append(template) 269 return toc_source
274 def get_formatted_text(self, context: dict | None = None) -> str: 275 index_body = self.get_element("text:index-body") 276 277 if index_body is None: 278 return "" 279 if context is None: 280 context = {} 281 if context.get("rst_mode"): 282 return "\n.. contents::\n\n" 283 284 result = [] 285 for element in index_body.children: 286 if element.tag == "text:index-title": 287 for child_element in element.children: 288 result.append(child_element.get_formatted_text(context).strip()) 289 else: 290 result.append(element.get_formatted_text(context).strip()) 291 return "\n".join(result)
This function should return a beautiful version of the text.
331 def set_toc_title( 332 self, 333 title: str, 334 style: str | None = None, 335 text_style: str | None = None, 336 ) -> None: 337 index_body = self.body 338 if index_body is None: 339 self.body = None 340 index_body = self.body 341 index_title = index_body.get_element(IndexTitle._tag) # type: ignore 342 if index_title is None: 343 name = f"{self.name}_Head" 344 index_title = IndexTitle( 345 name=name, style=style, title_text=title, text_style=text_style 346 ) 347 index_body.append(index_title) # type: ignore 348 else: 349 if style: 350 index_title.style = style # type: ignore 351 paragraph = index_title.get_paragraph() 352 if paragraph is None: 353 paragraph = Paragraph() 354 index_title.append(paragraph) 355 if text_style: 356 paragraph.style = text_style # type: ignore 357 paragraph.text = title
377 def fill( # noqa: C901 378 self, 379 document: Document | None = None, 380 use_default_styles: bool = True, 381 ) -> None: 382 """Fill the TOC with the titles found in the document. A TOC is not 383 contextual so it will catch all titles before and after its insertion. 384 If the TOC is not attached to a document, attach it beforehand or 385 provide one as argument. 386 387 For having a pretty TOC, let use_default_styles by default. 388 389 Arguments: 390 391 document -- Document 392 393 use_default_styles -- bool 394 """ 395 # Find the body 396 if document is not None: 397 body: Element | None = document.body 398 else: 399 body = self.document_body 400 if body is None: 401 raise ValueError("The TOC must be related to a document somehow") 402 403 # Save the title 404 index_body = self.body 405 title = index_body.get_element("text:index-title") # type: ignore 406 407 # Clean the old index-body 408 self.body = None 409 index_body = self.body 410 411 # Restore the title 412 if title and str(title): 413 index_body.insert(title, position=0) # type: ignore 414 415 # Insert default TOC style 416 if use_default_styles: 417 automatic_styles = body.get_element("//office:automatic-styles") 418 if isinstance(automatic_styles, Element): 419 for level in range(1, 11): 420 if ( 421 automatic_styles.get_style( 422 "paragraph", _toc_entry_style_name(level) 423 ) 424 is None 425 ): 426 level_style = default_toc_level_style(level) 427 automatic_styles.append(level_style) 428 429 # Auto-fill the index 430 outline_level = self.outline_level or 10 431 level_indexes: dict[int, int] = {} 432 for header in body.get_headers(): 433 level = header.get_attribute_integer("text:outline-level") or 0 434 if level is None or level > outline_level: 435 continue 436 number_str = self._header_numbering(level_indexes, level) 437 # Make the title with "1.2.3. Title" format 438 paragraph = Paragraph(f"{number_str} {header}") 439 if use_default_styles: 440 paragraph.style = _toc_entry_style_name(level) 441 index_body.append(paragraph) # type: ignore
Fill the TOC with the titles found in the document. A TOC is not contextual so it will catch all titles before and after its insertion. If the TOC is not attached to a document, attach it beforehand or provide one as argument.
For having a pretty TOC, let use_default_styles by default.
Arguments:
document -- Document
use_default_styles -- bool
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
194class Tab(Element): 195 """This element represents the [UNICODE] tab character (HORIZONTAL 196 TABULATION, U+0009). 197 198 The position attribute contains the number of the tab-stop to which 199 a tab character refers. The position 0 marks the start margin of a 200 paragraph. Note: The position attribute is only a hint to help non-layout 201 oriented consumers to determine the tab/tab-stop association. Layout 202 oriented consumers should determine the tab positions based on the style 203 information 204 """ 205 206 _tag = "text:tab" 207 _properties: tuple[PropDef, ...] = (PropDef("position", "text:tab-ref"),) 208 209 def __init__(self, position: int | None = None, **kwargs: Any) -> None: 210 """ 211 Arguments: 212 213 position -- int 214 """ 215 super().__init__(**kwargs) 216 if self._do_init and position is not None and position >= 0: 217 self.position = str(position)
This element represents the [UNICODE] tab character (HORIZONTAL TABULATION, U+0009).
The position attribute contains the number of the tab-stop to which a tab character refers. The position 0 marks the start margin of a paragraph. Note: The position attribute is only a hint to help non-layout oriented consumers to determine the tab/tab-stop association. Layout oriented consumers should determine the tab positions based on the style information
209 def __init__(self, position: int | None = None, **kwargs: Any) -> None: 210 """ 211 Arguments: 212 213 position -- int 214 """ 215 super().__init__(**kwargs) 216 if self._do_init and position is not None and position >= 0: 217 self.position = str(position)
Arguments:
position -- int
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
94class TabStopStyle(Element): 95 """ODF "style:tab-stop" 96 Base style for a TOC entryBase style for a TOC entry 97 """ 98 99 _tag = "style:tab-stop" 100 _properties = ( 101 PropDef("style_char", "style:char"), 102 PropDef("leader_color", "style:leader-color"), 103 PropDef("leader_style", "style:leader-style"), 104 PropDef("leader_text", "style:leader-text"), 105 PropDef("leader_text_style", "style:leader-text-style"), 106 PropDef("leader_type", "style:leader-type"), 107 PropDef("leader_width", "style:leader-width"), 108 PropDef("style_position", "style:position"), 109 PropDef("style_type", "style:type"), 110 ) 111 112 def __init__( # noqa: C901 113 self, 114 style_char: str | None = None, 115 leader_color: str | None = None, 116 leader_style: str | None = None, 117 leader_text: str | None = None, 118 leader_text_style: str | None = None, 119 leader_type: str | None = None, 120 leader_width: str | None = None, 121 style_position: str | None = None, 122 style_type: str | None = None, 123 **kwargs: Any, 124 ): 125 super().__init__(**kwargs) 126 if self._do_init: 127 if style_char: 128 self.style_char = style_char 129 if leader_color: 130 self.leader_color = leader_color 131 if leader_style: 132 self.leader_style = leader_style 133 if leader_text: 134 self.leader_text = leader_text 135 if leader_text_style: 136 self.leader_text_style = leader_text_style 137 if leader_type: 138 self.leader_type = leader_type 139 if leader_width: 140 self.leader_width = leader_width 141 if style_position: 142 self.style_position = style_position 143 if style_type: 144 self.style_type = style_type
ODF "style:tab-stop" Base style for a TOC entryBase style for a TOC entry
112 def __init__( # noqa: C901 113 self, 114 style_char: str | None = None, 115 leader_color: str | None = None, 116 leader_style: str | None = None, 117 leader_text: str | None = None, 118 leader_text_style: str | None = None, 119 leader_type: str | None = None, 120 leader_width: str | None = None, 121 style_position: str | None = None, 122 style_type: str | None = None, 123 **kwargs: Any, 124 ): 125 super().__init__(**kwargs) 126 if self._do_init: 127 if style_char: 128 self.style_char = style_char 129 if leader_color: 130 self.leader_color = leader_color 131 if leader_style: 132 self.leader_style = leader_style 133 if leader_text: 134 self.leader_text = leader_text 135 if leader_text_style: 136 self.leader_text_style = leader_text_style 137 if leader_type: 138 self.leader_type = leader_type 139 if leader_width: 140 self.leader_width = leader_width 141 if style_position: 142 self.style_position = style_position 143 if style_type: 144 self.style_type = style_type
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
284class Table(Element): 285 """ODF table "table:table" """ 286 287 _tag = "table:table" 288 _caching = True 289 _append = Element.append 290 291 def __init__( 292 self, 293 name: str | None = None, 294 width: int | None = None, 295 height: int | None = None, 296 protected: bool = False, 297 protection_key: str | None = None, 298 display: bool = True, 299 printable: bool = True, 300 print_ranges: list[str] | None = None, 301 style: str | None = None, 302 **kwargs: Any, 303 ) -> None: 304 """Create a table element, optionally prefilled with "height" rows of 305 "width" cells each. 306 307 If the table is to be protected, a protection key must be provided, 308 i.e. a hash value of the password. 309 310 If the table must not be displayed, set "display" to False. 311 312 If the table must not be printed, set "printable" to False. The table 313 will not be printed when it is not displayed, whatever the value of 314 this argument. 315 316 Ranges of cells to print can be provided as a list of cell ranges, 317 e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. 318 "E6:K12 P6:R12". 319 320 You can access and modify the XML tree manually, but you probably want 321 to use the API to access and alter cells. It will save you from 322 handling repetitions and the same number of cells for each row. 323 324 If you use both the table API and the XML API, you are on your own for 325 ensuiring model integrity. 326 327 Arguments: 328 329 name -- str 330 331 width -- int 332 333 height -- int 334 335 protected -- bool 336 337 protection_key -- str 338 339 display -- bool 340 341 printable -- bool 342 343 print_ranges -- list 344 345 style -- str 346 """ 347 super().__init__(**kwargs) 348 self._indexes = {} 349 self._indexes["_cmap"] = {} 350 self._indexes["_tmap"] = {} 351 if self._do_init: 352 self.name = name 353 if protected: 354 self.protected = protected 355 self.set_protection_key = protection_key 356 if not display: 357 self.displayed = display 358 if not printable: 359 self.printable = printable 360 if print_ranges: 361 self.print_ranges = print_ranges 362 if style: 363 self.style = style 364 # Prefill the table 365 if width is not None or height is not None: 366 width = width or 1 367 height = height or 1 368 # Column groups for style information 369 columns = Column(repeated=width) 370 self._append(columns) 371 for _i in range(height): 372 row = Row(width) 373 self._append(row) 374 self._compute_table_cache() 375 376 def __str__(self) -> str: 377 def write_content(csv_writer: object) -> None: 378 for values in self.iter_values(): 379 line = [] 380 for value in values: 381 if value is None: 382 value = "" 383 if isinstance(value, str): 384 value = value.strip() 385 line.append(value) 386 csv_writer.writerow(line) # type: ignore 387 388 out = StringIO(newline=os.linesep) 389 csv_writer = csv.writer( 390 out, 391 delimiter=" ", 392 doublequote=False, 393 escapechar="\\", 394 lineterminator=os.linesep, 395 quotechar='"', 396 quoting=csv.QUOTE_NONNUMERIC, 397 ) 398 write_content(csv_writer) 399 return out.getvalue() 400 401 def _translate_y_from_any(self, y: str | int) -> int: 402 # "3" (couting from 1) -> 2 (couting from 0) 403 return translate_from_any(y, self.height, 1) 404 405 def _translate_table_coordinates_list( 406 self, 407 coord: tuple | list, 408 ) -> tuple[int | None, ...]: 409 height = self.height 410 width = self.width 411 # assuming we got int values 412 if len(coord) == 1: 413 # It is a row 414 y = coord[0] 415 if y and y < 0: 416 y = increment(y, height) 417 return (None, y, None, y) 418 if len(coord) == 2: 419 # It is a row range, not a cell, because context is table 420 y = coord[0] 421 if y and y < 0: 422 y = increment(y, height) 423 t = coord[1] 424 if t and t < 0: 425 t = increment(t, height) 426 return (None, y, None, t) 427 # should be 4 int 428 x, y, z, t = coord 429 if x and x < 0: 430 x = increment(x, width) 431 if y and y < 0: 432 y = increment(y, height) 433 if z and z < 0: 434 z = increment(z, width) 435 if t and t < 0: 436 t = increment(t, height) 437 return (x, y, z, t) 438 439 def _translate_table_coordinates_str( 440 self, 441 coord_str: str, 442 ) -> tuple[int | None, ...]: 443 height = self.height 444 width = self.width 445 coord = convert_coordinates(coord_str) 446 if len(coord) == 2: 447 x, y = coord 448 if x and x < 0: 449 x = increment(x, width) 450 if y and y < 0: 451 y = increment(y, height) 452 # extent to an area : 453 return (x, y, x, y) 454 x, y, z, t = coord 455 if x and x < 0: 456 x = increment(x, width) 457 if y and y < 0: 458 y = increment(y, height) 459 if z and z < 0: 460 z = increment(z, width) 461 if t and t < 0: 462 t = increment(t, height) 463 return (x, y, z, t) 464 465 def _translate_table_coordinates( 466 self, 467 coord: tuple | list | str, 468 ) -> tuple[int | None, ...]: 469 if isinstance(coord, str): 470 return self._translate_table_coordinates_str(coord) 471 return self._translate_table_coordinates_list(coord) 472 473 def _translate_column_coordinates_str( 474 self, 475 coord_str: str, 476 ) -> tuple[int | None, ...]: 477 width = self.width 478 height = self.height 479 coord = convert_coordinates(coord_str) 480 if len(coord) == 2: 481 x, y = coord 482 if x and x < 0: 483 x = increment(x, width) 484 if y and y < 0: 485 y = increment(y, height) 486 # extent to an area : 487 return (x, y, x, y) 488 x, y, z, t = coord 489 if x and x < 0: 490 x = increment(x, width) 491 if y and y < 0: 492 y = increment(y, height) 493 if z and z < 0: 494 z = increment(z, width) 495 if t and t < 0: 496 t = increment(t, height) 497 return (x, y, z, t) 498 499 def _translate_column_coordinates_list( 500 self, 501 coord: tuple | list, 502 ) -> tuple[int | None, ...]: 503 width = self.width 504 height = self.height 505 # assuming we got int values 506 if len(coord) == 1: 507 # It is a column 508 x = coord[0] 509 if x and x < 0: 510 x = increment(x, width) 511 return (x, None, x, None) 512 if len(coord) == 2: 513 # It is a column range, not a cell, because context is table 514 x = coord[0] 515 if x and x < 0: 516 x = increment(x, width) 517 z = coord[1] 518 if z and z < 0: 519 z = increment(z, width) 520 return (x, None, z, None) 521 # should be 4 int 522 x, y, z, t = coord 523 if x and x < 0: 524 x = increment(x, width) 525 if y and y < 0: 526 y = increment(y, height) 527 if z and z < 0: 528 z = increment(z, width) 529 if t and t < 0: 530 t = increment(t, height) 531 return (x, y, z, t) 532 533 def _translate_column_coordinates( 534 self, 535 coord: tuple | list | str, 536 ) -> tuple[int | None, ...]: 537 if isinstance(coord, str): 538 return self._translate_column_coordinates_str(coord) 539 return self._translate_column_coordinates_list(coord) 540 541 def _translate_cell_coordinates( 542 self, 543 coord: tuple | list | str, 544 ) -> tuple[int | None, int | None]: 545 # we want an x,y result 546 coord = convert_coordinates(coord) 547 if len(coord) == 2: 548 x, y = coord 549 # If we got an area, take the first cell 550 elif len(coord) == 4: 551 x, y, z, t = coord 552 else: 553 raise ValueError(str(coord)) 554 if x and x < 0: 555 x = increment(x, self.width) 556 if y and y < 0: 557 y = increment(y, self.height) 558 return (x, y) 559 560 def _compute_table_cache(self) -> None: 561 idx_repeated_seq = self.elements_repeated_sequence( 562 _xpath_row, "table:number-rows-repeated" 563 ) 564 self._tmap = make_cache_map(idx_repeated_seq) 565 idx_repeated_seq = self.elements_repeated_sequence( 566 _xpath_column, "table:number-columns-repeated" 567 ) 568 self._cmap = make_cache_map(idx_repeated_seq) 569 570 def _update_width(self, row: Row) -> None: 571 """Synchronize the number of columns if the row is bigger. 572 573 Append, don't insert, not to disturb the current layout. 574 """ 575 diff = row.width - self.width 576 if diff > 0: 577 self.append_column(Column(repeated=diff)) 578 579 def _get_formatted_text_normal(self, context: dict | None) -> str: 580 result = [] 581 for row in self.traverse(): 582 for cell in row.traverse(): 583 value = cell.get_value(try_get_text=False) 584 # None ? 585 if value is None: 586 # Try with get_formatted_text on the elements 587 value = [] 588 for element in cell.children: 589 value.append(element.get_formatted_text(context)) 590 value = "".join(value) 591 else: 592 value = str(value) 593 result.append(value) 594 result.append("\n") 595 result.append("\n") 596 return "".join(result) 597 598 def _get_formatted_text_rst(self, context: dict) -> str: # noqa: C901 599 context["no_img_level"] += 1 600 # Strip the table => We must clone 601 table = self.clone 602 table.rstrip(aggressive=True) # type: ignore 603 604 # Fill the rows 605 rows = [] 606 cols_nb = 0 607 cols_size: dict[int, int] = {} 608 for odf_row in table.traverse(): # type: ignore 609 row = [] 610 for i, cell in enumerate(odf_row.traverse()): 611 value = cell.get_value(try_get_text=False) 612 # None ? 613 if value is None: 614 # Try with get_formatted_text on the elements 615 value = [] 616 for element in cell.children: 617 value.append(element.get_formatted_text(context)) 618 value = "".join(value) 619 else: 620 value = str(value) 621 value = value.strip() 622 # Strip the empty columns 623 if value: 624 cols_nb = max(cols_nb, i + 1) 625 # Compute the size of each columns (at least 2) 626 cols_size[i] = max(cols_size.get(i, 2), len(value)) 627 # Append 628 row.append(value) 629 rows.append(row) 630 631 # Nothing ? 632 if cols_nb == 0: 633 return "" 634 635 # Prevent a crash with empty columns (by example with images) 636 for col, size in cols_size.items(): 637 if size == 0: 638 cols_size[col] = 1 639 640 # Update cols_size 641 LINE_MAX = 100 642 COL_MIN = 16 643 644 free_size = LINE_MAX - (cols_nb - 1) * 3 - 4 645 real_size = sum([cols_size[i] for i in range(cols_nb)]) 646 if real_size > free_size: 647 factor = float(free_size) / real_size 648 649 for i in range(cols_nb): 650 old_size = cols_size[i] 651 652 # The cell is already small 653 if old_size <= COL_MIN: 654 continue 655 656 new_size = int(factor * old_size) 657 658 if new_size < COL_MIN: 659 new_size = COL_MIN 660 cols_size[i] = new_size 661 662 # Convert ! 663 result: list[str] = [""] 664 # Construct the first/last line 665 line: list[str] = [] 666 for i in range(cols_nb): 667 line.append("=" * cols_size[i]) 668 line.append(" ") 669 line_str = "".join(line) 670 671 # Add the lines 672 result.append(line_str) 673 for row in rows: 674 # Wrap the row 675 wrapped_row = [] 676 for i, value in enumerate(row[:cols_nb]): 677 wrapped_value = [] 678 for part in value.split("\n"): 679 # Hack to handle correctly the lists or the directives 680 subsequent_indent = "" 681 part_lstripped = part.lstrip() 682 if part_lstripped.startswith("-") or part_lstripped.startswith( 683 ".." 684 ): 685 subsequent_indent = " " * (len(part) - len(part.lstrip()) + 2) 686 wrapped_part = wrap( 687 part, width=cols_size[i], subsequent_indent=subsequent_indent 688 ) 689 if wrapped_part: 690 wrapped_value.extend(wrapped_part) 691 else: 692 wrapped_value.append("") 693 wrapped_row.append(wrapped_value) 694 695 # Append! 696 for j in range(max([1] + [len(values) for values in wrapped_row])): 697 txt_row: list[str] = [] 698 for i in range(cols_nb): 699 values = wrapped_row[i] if i < len(wrapped_row) else [] 700 701 # An empty cell ? 702 if len(values) - 1 < j or not values[j]: 703 if i == 0 and j == 0: 704 txt_row.append("..") 705 txt_row.append(" " * (cols_size[i] - 1)) 706 else: 707 txt_row.append(" " * (cols_size[i] + 1)) 708 continue 709 710 # Not empty 711 value = values[j] 712 txt_row.append(value) 713 txt_row.append(" " * (cols_size[i] - len(value) + 1)) 714 result.append("".join(txt_row)) 715 716 result.append(line_str) 717 result.append("") 718 result.append("") 719 result_str = "\n".join(result) 720 721 context["no_img_level"] -= 1 722 return result_str 723 724 def _translate_x_from_any(self, x: str | int) -> int: 725 return translate_from_any(x, self.width, 0) 726 727 # 728 # Public API 729 # 730 731 def append(self, something: Element | str) -> None: 732 """Dispatch .append() call to append_row() or append_column().""" 733 if isinstance(something, Row): 734 self.append_row(something) 735 elif isinstance(something, Column): 736 self.append_column(something) 737 else: 738 # probably still an error 739 self._append(something) 740 741 @property 742 def height(self) -> int: 743 """Get the current height of the table. 744 745 Return: int 746 """ 747 try: 748 height = self._tmap[-1] + 1 749 except Exception: 750 height = 0 751 return height 752 753 @property 754 def width(self) -> int: 755 """Get the current width of the table, measured on columns. 756 757 Rows may have different widths, use the Table API to ensure width 758 consistency. 759 760 Return: int 761 """ 762 # Columns are our reference for user expected width 763 764 try: 765 width = self._cmap[-1] + 1 766 except Exception: 767 width = 0 768 769 # columns = self._get_columns() 770 # repeated = self.xpath( 771 # 'table:table-column/@table:number-columns-repeated') 772 # unrepeated = len(columns) - len(repeated) 773 # ws = sum(int(r) for r in repeated) + unrepeated 774 # if w != ws: 775 # print "WARNING ws", ws, "w", w 776 777 return width 778 779 @property 780 def size(self) -> tuple[int, int]: 781 """Shortcut to get the current width and height of the table. 782 783 Return: (int, int) 784 """ 785 return self.width, self.height 786 787 @property 788 def name(self) -> str | None: 789 """Get / set the name of the table.""" 790 return self.get_attribute_string("table:name") 791 792 @name.setter 793 def name(self, name: str) -> None: 794 name = _table_name_check(name) 795 # first, update named ranges 796 # fixme : delete name ranges when deleting table, too. 797 for named_range in self.get_named_ranges(table_name=self.name): 798 named_range.set_table_name(name) 799 self.set_attribute("table:name", name) 800 801 @property 802 def protected(self) -> bool: 803 return bool(self.get_attribute("table:protected")) 804 805 @protected.setter 806 def protected(self, protect: bool) -> None: 807 self.set_attribute("table:protected", protect) 808 809 @property 810 def protection_key(self) -> str | None: 811 return self.get_attribute_string("table:protection-key") 812 813 @protection_key.setter 814 def protection_key(self, key: str) -> None: 815 self.set_attribute("table:protection-key", key) 816 817 @property 818 def displayed(self) -> bool: 819 return bool(self.get_attribute("table:display")) 820 821 @displayed.setter 822 def displayed(self, display: bool) -> None: 823 self.set_attribute("table:display", display) 824 825 @property 826 def printable(self) -> bool: 827 printable = self.get_attribute("table:print") 828 # Default value 829 if printable is None: 830 return True 831 return bool(printable) 832 833 @printable.setter 834 def printable(self, printable: bool) -> None: 835 self.set_attribute("table:print", printable) 836 837 @property 838 def print_ranges(self) -> list[str]: 839 ranges = self.get_attribute_string("table:print-ranges") 840 if isinstance(ranges, str): 841 return ranges.split() 842 return [] 843 844 @print_ranges.setter 845 def print_ranges(self, ranges: list[str] | None) -> None: 846 if isinstance(ranges, (list, tuple)): 847 self.set_attribute("table:print-ranges", " ".join(ranges)) 848 else: 849 self.set_attribute("table:print-ranges", ranges) 850 851 @property 852 def style(self) -> str | None: 853 """Get / set the style of the table 854 855 Return: str 856 """ 857 return self.get_attribute_string("table:style-name") 858 859 @style.setter 860 def style(self, style: str | Element) -> None: 861 self.set_style_attribute("table:style-name", style) 862 863 def get_formatted_text(self, context: dict | None = None) -> str: 864 if context and context["rst_mode"]: 865 return self._get_formatted_text_rst(context) 866 return self._get_formatted_text_normal(context) 867 868 def get_values( 869 self, 870 coord: tuple | list | str | None = None, 871 cell_type: str | None = None, 872 complete: bool = True, 873 get_type: bool = False, 874 flat: bool = False, 875 ) -> list: 876 """Get a matrix of values of the table. 877 878 Filter by coordinates will parse the area defined by the coordinates. 879 880 If 'cell_type' is used and 'complete' is True (default), missing values 881 are replaced by None. 882 Filter by ' cell_type = "all" ' will retrieve cells of any 883 type, aka non empty cells. 884 885 If 'cell_type' is None, complete is always True : with no cell type 886 queried, get_values() returns None for each empty cell, the length 887 each lists is equal to the width of the table. 888 889 If get_type is True, returns tuples (value, ODF type of value), or 890 (None, None) for empty cells with complete True. 891 892 If flat is True, the methods return a single list of all the values. 893 By default, flat is False. 894 895 Arguments: 896 897 coord -- str or tuple of int : coordinates of area 898 899 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 900 'currency', 'percentage' or 'all' 901 902 complete -- boolean 903 904 get_type -- boolean 905 906 Return: list of lists of Python types 907 """ 908 if coord: 909 x, y, z, t = self._translate_table_coordinates(coord) 910 else: 911 x = y = z = t = None 912 data = [] 913 for row in self.traverse(start=y, end=t): 914 if z is None: 915 width = self.width 916 else: 917 width = min(z + 1, self.width) 918 if x is not None: 919 width -= x 920 values = row.get_values( 921 (x, z), 922 cell_type=cell_type, 923 complete=complete, 924 get_type=get_type, 925 ) 926 # complete row to match request width 927 if complete: 928 if get_type: 929 values.extend([(None, None)] * (width - len(values))) 930 else: 931 values.extend([None] * (width - len(values))) 932 if flat: 933 data.extend(values) 934 else: 935 data.append(values) 936 return data 937 938 def iter_values( 939 self, 940 coord: tuple | list | str | None = None, 941 cell_type: str | None = None, 942 complete: bool = True, 943 get_type: bool = False, 944 ) -> Iterator[list]: 945 """Iterate through lines of Python values of the table. 946 947 Filter by coordinates will parse the area defined by the coordinates. 948 949 cell_type, complete, grt_type : see get_values() 950 951 952 953 Arguments: 954 955 coord -- str or tuple of int : coordinates of area 956 957 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 958 'currency', 'percentage' or 'all' 959 960 complete -- boolean 961 962 get_type -- boolean 963 964 Return: iterator of lists 965 """ 966 if coord: 967 x, y, z, t = self._translate_table_coordinates(coord) 968 else: 969 x = y = z = t = None 970 for row in self.traverse(start=y, end=t): 971 if z is None: 972 width = self.width 973 else: 974 width = min(z + 1, self.width) 975 if x is not None: 976 width -= x 977 values = row.get_values( 978 (x, z), 979 cell_type=cell_type, 980 complete=complete, 981 get_type=get_type, 982 ) 983 # complete row to match column width 984 if complete: 985 if get_type: 986 values.extend([(None, None)] * (width - len(values))) 987 else: 988 values.extend([None] * (width - len(values))) 989 yield values 990 991 def set_values( 992 self, 993 values: list, 994 coord: tuple | list | str | None = None, 995 style: str | None = None, 996 cell_type: str | None = None, 997 currency: str | None = None, 998 ) -> None: 999 """Set the value of cells in the table, from the 'coord' position 1000 with values. 1001 1002 'coord' is the coordinate of the upper left cell to be modified by 1003 values. If 'coord' is None, default to the position (0,0) ("A1"). 1004 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1005 area is used as coordinate. 1006 1007 The table is *not* cleared before the operation, to reset the table 1008 before setting values, use table.clear(). 1009 1010 A list of lists is expected, with as many lists as rows, and as many 1011 items in each sublist as cells to be setted. None values in the list 1012 will create empty cells with no cell type (but eventually a style). 1013 1014 Arguments: 1015 1016 coord -- tuple or str 1017 1018 values -- list of lists of python types 1019 1020 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1021 'string' or 'time' 1022 1023 currency -- three-letter str 1024 1025 style -- str 1026 """ 1027 if coord: 1028 x, y = self._translate_cell_coordinates(coord) 1029 else: 1030 x = y = 0 1031 if y is None: 1032 y = 0 1033 if x is None: 1034 x = 0 1035 y -= 1 1036 for row_values in values: 1037 y += 1 1038 if not row_values: 1039 continue 1040 row = self.get_row(y, clone=True) 1041 repeated = row.repeated or 1 1042 if repeated >= 2: 1043 row.repeated = None 1044 row.set_values( 1045 row_values, 1046 start=x, 1047 cell_type=cell_type, 1048 currency=currency, 1049 style=style, 1050 ) 1051 self.set_row(y, row, clone=False) 1052 self._update_width(row) 1053 1054 def rstrip(self, aggressive: bool = False) -> None: 1055 """Remove *in-place* empty rows below and empty cells at the right of 1056 the table. Cells are empty if they contain no value or it evaluates 1057 to False, and no style. 1058 1059 If aggressive is True, empty cells with style are removed too. 1060 1061 Argument: 1062 1063 aggressive -- bool 1064 """ 1065 # Step 1: remove empty rows below the table 1066 for row in reversed(self._get_rows()): 1067 if row.is_empty(aggressive=aggressive): 1068 row.parent.delete(row) # type: ignore 1069 else: 1070 break 1071 # Step 2: rstrip remaining rows 1072 max_width = 0 1073 for row in self._get_rows(): 1074 row.rstrip(aggressive=aggressive) 1075 # keep count of the biggest row 1076 max_width = max(max_width, row.width) 1077 # raz cache of rows 1078 self._indexes["_tmap"] = {} 1079 # Step 3: trim columns to match max_width 1080 columns = self._get_columns() 1081 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1082 if not isinstance(repeated_cols, list): 1083 raise TypeError 1084 unrepeated = len(columns) - len(repeated_cols) 1085 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1086 diff = column_width - max_width 1087 if diff > 0: 1088 for column in reversed(columns): 1089 repeated = column.repeated or 1 1090 repeated = repeated - diff 1091 if repeated > 0: 1092 column.repeated = repeated 1093 break 1094 else: 1095 column.parent.delete(column) 1096 diff = -repeated 1097 if diff == 0: 1098 break 1099 # raz cache of columns 1100 self._indexes["_cmap"] = {} 1101 self._compute_table_cache() 1102 1103 def transpose(self, coord: tuple | list | str | None = None) -> None: # noqa: C901 1104 """Swap *in-place* rows and columns of the table. 1105 1106 If 'coord' is not None, apply transpose only to the area defined by the 1107 coordinates. Beware, if area is not square, some cells mays be over 1108 written during the process. 1109 1110 Arguments: 1111 1112 coord -- str or tuple of int : coordinates of area 1113 1114 start -- int or str 1115 """ 1116 data = [] 1117 if coord is None: 1118 for row in self.traverse(): 1119 data.append(list(row.traverse())) 1120 transposed_data = zip_longest(*data) 1121 self.clear() 1122 # new_rows = [] 1123 for row_cells in transposed_data: 1124 if not isiterable(row_cells): 1125 row_cells = (row_cells,) 1126 row = Row() 1127 row.extend_cells(row_cells) 1128 self.append_row(row, clone=False) 1129 self._compute_table_cache() 1130 else: 1131 x, y, z, t = self._translate_table_coordinates(coord) 1132 if x is None: 1133 x = 0 1134 else: 1135 x = min(x, self.width - 1) 1136 if z is None: 1137 z = self.width - 1 1138 else: 1139 z = min(z, self.width - 1) 1140 if y is None: 1141 y = 0 1142 else: 1143 y = min(y, self.height - 1) 1144 if t is None: 1145 t = self.height - 1 1146 else: 1147 t = min(t, self.height - 1) 1148 for row in self.traverse(start=y, end=t): 1149 data.append(list(row.traverse(start=x, end=z))) 1150 transposed_data = zip_longest(*data) 1151 # clear locally 1152 w = z - x + 1 1153 h = t - y + 1 1154 if w != h: 1155 nones = [[None] * w for i in range(h)] 1156 self.set_values(nones, coord=(x, y, z, t)) 1157 # put transposed 1158 filtered_data: list[tuple[Cell]] = [] 1159 for row_cells in transposed_data: 1160 if isinstance(row_cells, (list, tuple)): 1161 filtered_data.append(row_cells) 1162 else: 1163 filtered_data.append((row_cells,)) 1164 self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1)) 1165 self._compute_table_cache() 1166 1167 def is_empty(self, aggressive: bool = False) -> bool: 1168 """Return whether every cell in the table has no value or the value 1169 evaluates to False (empty string), and no style. 1170 1171 If aggressive is True, empty cells with style are considered empty. 1172 1173 Arguments: 1174 1175 aggressive -- bool 1176 """ 1177 return all(row.is_empty(aggressive=aggressive) for row in self._get_rows()) 1178 1179 # 1180 # Rows 1181 # 1182 1183 def _get_rows(self) -> list[Row]: 1184 return self.get_elements(_xpath_row) # type: ignore 1185 1186 def traverse( # noqa: C901 1187 self, 1188 start: int | None = None, 1189 end: int | None = None, 1190 ) -> Iterator[Row]: 1191 """Yield as many row elements as expected rows in the table, i.e. 1192 expand repetitions by returning the same row as many times as 1193 necessary. 1194 1195 Arguments: 1196 1197 start -- int 1198 1199 end -- int 1200 1201 Copies are returned, use set_row() to push them back. 1202 """ 1203 idx = -1 1204 before = -1 1205 y = 0 1206 if start is None and end is None: 1207 for juska in self._tmap: 1208 idx += 1 1209 if idx in self._indexes["_tmap"]: 1210 row = self._indexes["_tmap"][idx] 1211 else: 1212 row = self._get_element_idx2(_xpath_row_idx, idx) 1213 self._indexes["_tmap"][idx] = row 1214 repeated = juska - before 1215 before = juska 1216 for _i in range(repeated or 1): 1217 # Return a copy without the now obsolete repetition 1218 row = row.clone 1219 row.y = y 1220 y += 1 1221 if repeated > 1: 1222 row.repeated = None 1223 yield row 1224 else: 1225 if start is None: 1226 start = 0 1227 start = max(0, start) 1228 if end is None: 1229 try: 1230 end = self._tmap[-1] 1231 except Exception: 1232 end = -1 1233 start_map = find_odf_idx(self._tmap, start) 1234 if start_map is None: 1235 return 1236 if start_map > 0: 1237 before = self._tmap[start_map - 1] 1238 idx = start_map - 1 1239 before = start - 1 1240 y = start 1241 for juska in self._tmap[start_map:]: 1242 idx += 1 1243 if idx in self._indexes["_tmap"]: 1244 row = self._indexes["_tmap"][idx] 1245 else: 1246 row = self._get_element_idx2(_xpath_row_idx, idx) 1247 self._indexes["_tmap"][idx] = row 1248 repeated = juska - before 1249 before = juska 1250 for _i in range(repeated or 1): 1251 if y <= end: 1252 row = row.clone 1253 row.y = y 1254 y += 1 1255 if repeated > 1 or (y == start and start > 0): 1256 row.repeated = None 1257 yield row 1258 1259 def get_rows( 1260 self, 1261 coord: tuple | list | str | None = None, 1262 style: str | None = None, 1263 content: str | None = None, 1264 ) -> list[Row]: 1265 """Get the list of rows matching the criteria. 1266 1267 Filter by coordinates will parse the area defined by the coordinates. 1268 1269 Arguments: 1270 1271 coord -- str or tuple of int : coordinates of rows 1272 1273 content -- str regex 1274 1275 style -- str 1276 1277 Return: list of rows 1278 """ 1279 if coord: 1280 _x, y, _z, t = self._translate_table_coordinates(coord) 1281 else: 1282 y = t = None 1283 # fixme : not clones ? 1284 if not content and not style: 1285 return list(self.traverse(start=y, end=t)) 1286 rows = [] 1287 for row in self.traverse(start=y, end=t): 1288 if content and not row.match(content): 1289 continue 1290 if style and style != row.style: 1291 continue 1292 rows.append(row) 1293 return rows 1294 1295 def _get_row2(self, y: int, clone: bool = True, create: bool = True) -> Row: 1296 if y >= self.height: 1297 if create: 1298 return Row() 1299 raise ValueError("Row not found") 1300 row = self._get_row2_base(y) 1301 if row is None: 1302 raise ValueError("Row not found") 1303 if clone: 1304 return row.clone 1305 return row 1306 1307 def _get_row2_base(self, y: int) -> Row | None: 1308 idx = find_odf_idx(self._tmap, y) 1309 if idx is not None: 1310 if idx in self._indexes["_tmap"]: 1311 row = self._indexes["_tmap"][idx] 1312 else: 1313 row = self._get_element_idx2(_xpath_row_idx, idx) 1314 self._indexes["_tmap"][idx] = row 1315 return row 1316 return None 1317 1318 def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row: 1319 """Get the row at the given "y" position. 1320 1321 Position start at 0. So cell A4 is on row 3. 1322 1323 A copy is returned, use set_cell() to push it back. 1324 1325 Arguments: 1326 1327 y -- int or str 1328 1329 Return: Row 1330 """ 1331 # fixme : keep repeat ? maybe an option to functions : "raw=False" 1332 y = self._translate_y_from_any(y) 1333 row = self._get_row2(y, clone=clone, create=create) 1334 if row is None: 1335 raise ValueError("Row not found") 1336 row.y = y 1337 return row 1338 1339 def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row: 1340 """Replace the row at the given position with the new one. Repetions of 1341 the old row will be adjusted. 1342 1343 If row is None, a new empty row is created. 1344 1345 Position start at 0. So cell A4 is on row 3. 1346 1347 Arguments: 1348 1349 y -- int or str 1350 1351 row -- Row 1352 1353 returns the row, with updated row.y 1354 """ 1355 if row is None: 1356 row = Row() 1357 repeated = 1 1358 clone = False 1359 else: 1360 repeated = row.repeated or 1 1361 y = self._translate_y_from_any(y) 1362 row.y = y 1363 # Outside the defined table ? 1364 diff = y - self.height 1365 if diff == 0: 1366 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1367 elif diff > 0: 1368 self.append_row(Row(repeated=diff), _repeated=diff, clone=clone) 1369 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1370 else: 1371 # Inside the defined table 1372 row_back = set_item_in_vault( # type: ignore 1373 y, row, self, _xpath_row_idx, "_tmap", clone=clone 1374 ) 1375 # print self.serialize(True) 1376 # Update width if necessary 1377 self._update_width(row_back) 1378 return row_back 1379 1380 def insert_row( 1381 self, y: str | int, row: Row | None = None, clone: bool = True 1382 ) -> Row: 1383 """Insert the row before the given "y" position. If no row is given, 1384 an empty one is created. 1385 1386 Position start at 0. So cell A4 is on row 3. 1387 1388 If row is None, a new empty row is created. 1389 1390 Arguments: 1391 1392 y -- int or str 1393 1394 row -- Row 1395 1396 returns the row, with updated row.y 1397 """ 1398 if row is None: 1399 row = Row() 1400 clone = False 1401 y = self._translate_y_from_any(y) 1402 diff = y - self.height 1403 if diff < 0: 1404 row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap") 1405 elif diff == 0: 1406 row_back = self.append_row(row, clone=clone) 1407 else: 1408 self.append_row(Row(repeated=diff), _repeated=diff, clone=False) 1409 row_back = self.append_row(row, clone=clone) 1410 row_back.y = y # type: ignore 1411 # Update width if necessary 1412 self._update_width(row_back) # type: ignore 1413 return row_back # type: ignore 1414 1415 def extend_rows(self, rows: list[Row] | None = None) -> None: 1416 """Append a list of rows at the end of the table. 1417 1418 Arguments: 1419 1420 rows -- list of Row 1421 """ 1422 if rows is None: 1423 rows = [] 1424 self.extend(rows) 1425 self._compute_table_cache() 1426 # Update width if necessary 1427 width = self.width 1428 for row in self.traverse(): 1429 if row.width > width: 1430 width = row.width 1431 diff = width - self.width 1432 if diff > 0: 1433 self.append_column(Column(repeated=diff)) 1434 1435 def append_row( 1436 self, 1437 row: Row | None = None, 1438 clone: bool = True, 1439 _repeated: int | None = None, 1440 ) -> Row: 1441 """Append the row at the end of the table. If no row is given, an 1442 empty one is created. 1443 1444 Position start at 0. So cell A4 is on row 3. 1445 1446 Note the columns are automatically created when the first row is 1447 inserted in an empty table. So better insert a filled row. 1448 1449 Arguments: 1450 1451 row -- Row 1452 1453 _repeated -- (optional), repeated value of the row 1454 1455 returns the row, with updated row.y 1456 """ 1457 if row is None: 1458 row = Row() 1459 _repeated = 1 1460 elif clone: 1461 row = row.clone 1462 # Appending a repeated row accepted 1463 # Do not insert next to the last row because it could be in a group 1464 self._append(row) 1465 if _repeated is None: 1466 _repeated = row.repeated or 1 1467 self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated) 1468 row.y = self.height - 1 1469 # Initialize columns 1470 if not self._get_columns(): 1471 repeated = row.width 1472 self.insert(Column(repeated=repeated), position=0) 1473 self._compute_table_cache() 1474 # Update width if necessary 1475 self._update_width(row) 1476 return row 1477 1478 def delete_row(self, y: int | str) -> None: 1479 """Delete the row at the given "y" position. 1480 1481 Position start at 0. So cell A4 is on row 3. 1482 1483 Arguments: 1484 1485 y -- int or str 1486 """ 1487 y = self._translate_y_from_any(y) 1488 # Outside the defined table 1489 if y >= self.height: 1490 return 1491 # Inside the defined table 1492 delete_item_in_vault(y, self, _xpath_row_idx, "_tmap") 1493 1494 def get_row_values( 1495 self, 1496 y: int | str, 1497 cell_type: str | None = None, 1498 complete: bool = True, 1499 get_type: bool = False, 1500 ) -> list: 1501 """Shortcut to get the list of Python values for the cells of the row 1502 at the given "y" position. 1503 1504 Position start at 0. So cell A4 is on row 3. 1505 1506 Filter by cell_type, with cell_type 'all' will retrieve cells of any 1507 type, aka non empty cells. 1508 If cell_type and complete is True, replace missing values by None. 1509 1510 If get_type is True, returns a tuple (value, ODF type of value) 1511 1512 Arguments: 1513 1514 y -- int, str 1515 1516 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1517 'currency', 'percentage' or 'all' 1518 1519 complete -- boolean 1520 1521 get_type -- boolean 1522 1523 Return: list of lists of Python types 1524 """ 1525 values = self.get_row(y, clone=False).get_values( 1526 cell_type=cell_type, complete=complete, get_type=get_type 1527 ) 1528 # complete row to match column width 1529 if complete: 1530 if get_type: 1531 values.extend([(None, None)] * (self.width - len(values))) 1532 else: 1533 values.extend([None] * (self.width - len(values))) 1534 return values 1535 1536 def set_row_values( 1537 self, 1538 y: int | str, 1539 values: list, 1540 cell_type: str | None = None, 1541 currency: str | None = None, 1542 style: str | None = None, 1543 ) -> Row: 1544 """Shortcut to set the values of *all* cells of the row at the given 1545 "y" position. 1546 1547 Position start at 0. So cell A4 is on row 3. 1548 1549 Arguments: 1550 1551 y -- int or str 1552 1553 values -- list of Python types 1554 1555 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1556 'string' or 'time' 1557 1558 currency -- three-letter str 1559 1560 style -- str 1561 1562 returns the row, with updated row.y 1563 """ 1564 row = Row() # needed if clones rows 1565 row.set_values(values, style=style, cell_type=cell_type, currency=currency) 1566 return self.set_row(y, row) # needed if clones rows 1567 1568 def set_row_cells(self, y: int | str, cells: list | None = None) -> Row: 1569 """Shortcut to set *all* the cells of the row at the given 1570 "y" position. 1571 1572 Position start at 0. So cell A4 is on row 3. 1573 1574 Arguments: 1575 1576 y -- int or str 1577 1578 cells -- list of Python types 1579 1580 style -- str 1581 1582 returns the row, with updated row.y 1583 """ 1584 if cells is None: 1585 cells = [] 1586 row = Row() # needed if clones rows 1587 row.extend_cells(cells) 1588 return self.set_row(y, row) # needed if clones rows 1589 1590 def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool: 1591 """Return wether every cell in the row at the given "y" position has 1592 no value or the value evaluates to False (empty string), and no style. 1593 1594 Position start at 0. So cell A4 is on row 3. 1595 1596 If aggressive is True, empty cells with style are considered empty. 1597 1598 Arguments: 1599 1600 y -- int or str 1601 1602 aggressive -- bool 1603 """ 1604 return self.get_row(y, clone=False).is_empty(aggressive=aggressive) 1605 1606 # 1607 # Cells 1608 # 1609 1610 def get_cells( 1611 self, 1612 coord: tuple | list | str | None = None, 1613 cell_type: str | None = None, 1614 style: str | None = None, 1615 content: str | None = None, 1616 flat: bool = False, 1617 ) -> list: 1618 """Get the cells matching the criteria. If 'coord' is None, 1619 parse the whole table, else parse the area defined by 'coord'. 1620 1621 Filter by cell_type = "all" will retrieve cells of any 1622 type, aka non empty cells. 1623 1624 If flat is True (default is False), the method return a single list 1625 of all the values, else a list of lists of cells. 1626 1627 if cell_type, style and content are None, get_cells() will return 1628 the exact number of cells of the area, including empty cells. 1629 1630 Arguments: 1631 1632 coordinates -- str or tuple of int : coordinates of area 1633 1634 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1635 'currency', 'percentage' or 'all' 1636 1637 content -- str regex 1638 1639 style -- str 1640 1641 flat -- boolean 1642 1643 Return: list of tuples 1644 """ 1645 if coord: 1646 x, y, z, t = self._translate_table_coordinates(coord) 1647 else: 1648 x = y = z = t = None 1649 if flat: 1650 cells: list[Cell] = [] 1651 for row in self.traverse(start=y, end=t): 1652 row_cells = row.get_cells( 1653 coord=(x, z), 1654 cell_type=cell_type, 1655 style=style, 1656 content=content, 1657 ) 1658 cells.extend(row_cells) 1659 return cells 1660 else: 1661 lcells: list[list[Cell]] = [] 1662 for row in self.traverse(start=y, end=t): 1663 row_cells = row.get_cells( 1664 coord=(x, z), 1665 cell_type=cell_type, 1666 style=style, 1667 content=content, 1668 ) 1669 lcells.append(row_cells) 1670 return lcells 1671 1672 def get_cell( 1673 self, 1674 coord: tuple | list | str, 1675 clone: bool = True, 1676 keep_repeated: bool = True, 1677 ) -> Cell: 1678 """Get the cell at the given coordinates. 1679 1680 They are either a 2-uplet of (x, y) starting from 0, or a 1681 human-readable position like "C4". 1682 1683 A copy is returned, use ``set_cell`` to push it back. 1684 1685 Arguments: 1686 1687 coord -- (int, int) or str 1688 1689 Return: Cell 1690 """ 1691 x, y = self._translate_cell_coordinates(coord) 1692 if x is None: 1693 raise ValueError 1694 if y is None: 1695 raise ValueError 1696 # Outside the defined table 1697 if y >= self.height: 1698 cell = Cell() 1699 else: 1700 # Inside the defined table 1701 row = self._get_row2_base(y) 1702 if row is None: 1703 raise ValueError 1704 read_cell = row.get_cell(x, clone=clone) 1705 if read_cell is None: 1706 raise ValueError 1707 cell = read_cell 1708 if not keep_repeated: 1709 repeated = cell.repeated or 1 1710 if repeated >= 2: 1711 cell.repeated = None 1712 cell.x = x 1713 cell.y = y 1714 return cell 1715 1716 def get_value( 1717 self, 1718 coord: tuple | list | str, 1719 get_type: bool = False, 1720 ) -> Any: 1721 """Shortcut to get the Python value of the cell at the given 1722 coordinates. 1723 1724 If get_type is True, returns the tuples (value, ODF type) 1725 1726 coord is either a 2-uplet of (x, y) starting from 0, or a 1727 human-readable position like "C4". If an Area is given, the upper 1728 left position is used as coord. 1729 1730 Arguments: 1731 1732 coord -- (int, int) or str : coordinate 1733 1734 Return: Python type 1735 """ 1736 x, y = self._translate_cell_coordinates(coord) 1737 if x is None: 1738 raise ValueError 1739 if y is None: 1740 raise ValueError 1741 # Outside the defined table 1742 if y >= self.height: 1743 if get_type: 1744 return (None, None) 1745 return None 1746 else: 1747 # Inside the defined table 1748 row = self._get_row2_base(y) 1749 if row is None: 1750 raise ValueError 1751 cell = row._get_cell2_base(x) 1752 if cell is None: 1753 if get_type: 1754 return (None, None) 1755 return None 1756 return cell.get_value(get_type=get_type) 1757 1758 def set_cell( 1759 self, 1760 coord: tuple | list | str, 1761 cell: Cell | None = None, 1762 clone: bool = True, 1763 ) -> Cell: 1764 """Replace a cell of the table at the given coordinates. 1765 1766 They are either a 2-uplet of (x, y) starting from 0, or a 1767 human-readable position like "C4". 1768 1769 Arguments: 1770 1771 coord -- (int, int) or str : coordinate 1772 1773 cell -- Cell 1774 1775 return the cell, with x and y updated 1776 """ 1777 if cell is None: 1778 cell = Cell() 1779 clone = False 1780 x, y = self._translate_cell_coordinates(coord) 1781 if x is None: 1782 raise ValueError 1783 if y is None: 1784 raise ValueError 1785 cell.x = x 1786 cell.y = y 1787 if y >= self.height: 1788 row = Row() 1789 cell_back = row.set_cell(x, cell, clone=clone) 1790 self.set_row(y, row, clone=False) 1791 else: 1792 row_read = self._get_row2_base(y) 1793 if row_read is None: 1794 raise ValueError 1795 row = row_read 1796 row.y = y 1797 repeated = row.repeated or 1 1798 if repeated > 1: 1799 row = row.clone 1800 row.repeated = None 1801 cell_back = row.set_cell(x, cell, clone=clone) 1802 self.set_row(y, row, clone=False) 1803 else: 1804 cell_back = row.set_cell(x, cell, clone=clone) 1805 # Update width if necessary, since we don't use set_row 1806 self._update_width(row) 1807 return cell_back 1808 1809 def set_cells( 1810 self, 1811 cells: list[list[Cell]] | list[tuple[Cell]], 1812 coord: tuple | list | str | None = None, 1813 clone: bool = True, 1814 ) -> None: 1815 """Set the cells in the table, from the 'coord' position. 1816 1817 'coord' is the coordinate of the upper left cell to be modified by 1818 values. If 'coord' is None, default to the position (0,0) ("A1"). 1819 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1820 area is used as coordinate. 1821 1822 The table is *not* cleared before the operation, to reset the table 1823 before setting cells, use table.clear(). 1824 1825 A list of lists is expected, with as many lists as rows to be set, and 1826 as many cell in each sublist as cells to be setted in the row. 1827 1828 Arguments: 1829 1830 cells -- list of list of cells 1831 1832 coord -- tuple or str 1833 1834 values -- list of lists of python types 1835 """ 1836 if coord: 1837 x, y = self._translate_cell_coordinates(coord) 1838 else: 1839 x = y = 0 1840 if y is None: 1841 y = 0 1842 if x is None: 1843 x = 0 1844 y -= 1 1845 for row_cells in cells: 1846 y += 1 1847 if not row_cells: 1848 continue 1849 row = self.get_row(y, clone=True) 1850 repeated = row.repeated or 1 1851 if repeated >= 2: 1852 row.repeated = None 1853 row.set_cells(row_cells, start=x, clone=clone) 1854 self.set_row(y, row, clone=False) 1855 self._update_width(row) 1856 1857 def set_value( 1858 self, 1859 coord: tuple | list | str, 1860 value: Any, 1861 cell_type: str | None = None, 1862 currency: str | None = None, 1863 style: str | None = None, 1864 ) -> None: 1865 """Set the Python value of the cell at the given coordinates. 1866 1867 They are either a 2-uplet of (x, y) starting from 0, or a 1868 human-readable position like "C4". 1869 1870 Arguments: 1871 1872 coord -- (int, int) or str 1873 1874 value -- Python type 1875 1876 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1877 'string' or 'time' 1878 1879 currency -- three-letter str 1880 1881 style -- str 1882 1883 """ 1884 self.set_cell( 1885 coord, 1886 Cell(value, cell_type=cell_type, currency=currency, style=style), 1887 clone=False, 1888 ) 1889 1890 def set_cell_image( 1891 self, 1892 coord: tuple | list | str, 1893 image_frame: Frame, 1894 doc_type: str | None = None, 1895 ) -> None: 1896 """Do all the magic to display an image in the cell at the given 1897 coordinates. 1898 1899 They are either a 2-uplet of (x, y) starting from 0, or a 1900 human-readable position like "C4". 1901 1902 The frame element must contain the expected image position and 1903 dimensions. 1904 1905 DrawImage insertion depends on the document type, so the type must be 1906 provided or the table element must be already attached to a document. 1907 1908 Arguments: 1909 1910 coord -- (int, int) or str 1911 1912 image_frame -- Frame including an image 1913 1914 doc_type -- 'spreadsheet' or 'text' 1915 """ 1916 # Test document type 1917 if doc_type is None: 1918 body = self.document_body 1919 if body is None: 1920 raise ValueError("document type not found") 1921 doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get( 1922 body.tag 1923 ) 1924 if doc_type is None: 1925 raise ValueError("document type not supported for images") 1926 # We need the end address of the image 1927 x, y = self._translate_cell_coordinates(coord) 1928 if x is None: 1929 raise ValueError 1930 if y is None: 1931 raise ValueError 1932 cell = self.get_cell((x, y)) 1933 image_frame = image_frame.clone # type: ignore 1934 # Remove any previous paragraph, frame, etc. 1935 for child in cell.children: 1936 cell.delete(child) 1937 # Now it all depends on the document type 1938 if doc_type == "spreadsheet": 1939 image_frame.anchor_type = "char" 1940 # The frame needs end coordinates 1941 width, height = image_frame.size 1942 image_frame.set_attribute("table:end-x", width) 1943 image_frame.set_attribute("table:end-y", height) 1944 # FIXME what happens when the address changes? 1945 address = f"{self.name}.{digit_to_alpha(x)}{y + 1}" 1946 image_frame.set_attribute("table:end-cell-address", address) 1947 # The frame is directly in the cell 1948 cell.append(image_frame) 1949 elif doc_type == "text": 1950 # The frame must be in a paragraph 1951 cell.set_value("") 1952 paragraph = cell.get_element("text:p") 1953 if paragraph is None: 1954 raise ValueError 1955 paragraph.append(image_frame) 1956 self.set_cell(coord, cell) 1957 1958 def insert_cell( 1959 self, 1960 coord: tuple | list | str, 1961 cell: Cell | None = None, 1962 clone: bool = True, 1963 ) -> Cell: 1964 """Insert the given cell at the given coordinates. If no cell is 1965 given, an empty one is created. 1966 1967 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 1968 human-readable position like "C4". 1969 1970 Cells on the right are shifted. Other rows remain untouched. 1971 1972 Arguments: 1973 1974 coord -- (int, int) or str 1975 1976 cell -- Cell 1977 1978 returns the cell with x and y updated 1979 """ 1980 if cell is None: 1981 cell = Cell() 1982 clone = False 1983 if clone: 1984 cell = cell.clone 1985 x, y = self._translate_cell_coordinates(coord) 1986 if x is None: 1987 raise ValueError 1988 if y is None: 1989 raise ValueError 1990 row = self._get_row2(y, clone=True) 1991 row.y = y 1992 row.repeated = None 1993 cell_back = row.insert_cell(x, cell, clone=False) 1994 self.set_row(y, row, clone=False) 1995 # Update width if necessary 1996 self._update_width(row) 1997 return cell_back 1998 1999 def append_cell( 2000 self, 2001 y: int | str, 2002 cell: Cell | None = None, 2003 clone: bool = True, 2004 ) -> Cell: 2005 """Append the given cell at the "y" coordinate. Repeated cells are 2006 accepted. If no cell is given, an empty one is created. 2007 2008 Position start at 0. So cell A4 is on row 3. 2009 2010 Other rows remain untouched. 2011 2012 Arguments: 2013 2014 y -- int or str 2015 2016 cell -- Cell 2017 2018 returns the cell with x and y updated 2019 """ 2020 if cell is None: 2021 cell = Cell() 2022 clone = False 2023 if clone: 2024 cell = cell.clone 2025 y = self._translate_y_from_any(y) 2026 row = self._get_row2(y) 2027 row.y = y 2028 cell_back = row.append_cell(cell, clone=False) 2029 self.set_row(y, row) 2030 # Update width if necessary 2031 self._update_width(row) 2032 return cell_back 2033 2034 def delete_cell(self, coord: tuple | list | str) -> None: 2035 """Delete the cell at the given coordinates, so that next cells are 2036 shifted to the left. 2037 2038 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2039 human-readable position like "C4". 2040 2041 Use set_value() for erasing value. 2042 2043 Arguments: 2044 2045 coord -- (int, int) or str 2046 """ 2047 x, y = self._translate_cell_coordinates(coord) 2048 if x is None: 2049 raise ValueError 2050 if y is None: 2051 raise ValueError 2052 # Outside the defined table 2053 if y >= self.height: 2054 return 2055 # Inside the defined table 2056 row = self._get_row2_base(y) 2057 if row is None: 2058 raise ValueError 2059 row.delete_cell(x) 2060 # self.set_row(y, row) 2061 2062 # Columns 2063 2064 def _get_columns(self) -> list: 2065 return self.get_elements(_xpath_column) 2066 2067 def traverse_columns( # noqa: C901 2068 self, 2069 start: int | None = None, 2070 end: int | None = None, 2071 ) -> Iterator[Column]: 2072 """Yield as many column elements as expected columns in the table, 2073 i.e. expand repetitions by returning the same column as many times as 2074 necessary. 2075 2076 Arguments: 2077 2078 start -- int 2079 2080 end -- int 2081 2082 Copies are returned, use set_column() to push them back. 2083 """ 2084 idx = -1 2085 before = -1 2086 x = 0 2087 if start is None and end is None: 2088 for juska in self._cmap: 2089 idx += 1 2090 if idx in self._indexes["_cmap"]: 2091 column = self._indexes["_cmap"][idx] 2092 else: 2093 column = self._get_element_idx2(_xpath_column_idx, idx) 2094 self._indexes["_cmap"][idx] = column 2095 repeated = juska - before 2096 before = juska 2097 for _i in range(repeated or 1): 2098 # Return a copy without the now obsolete repetition 2099 column = column.clone 2100 column.x = x 2101 x += 1 2102 if repeated > 1: 2103 column.repeated = None 2104 yield column 2105 else: 2106 if start is None: 2107 start = 0 2108 start = max(0, start) 2109 if end is None: 2110 try: 2111 end = self._cmap[-1] 2112 except Exception: 2113 end = -1 2114 start_map = find_odf_idx(self._cmap, start) 2115 if start_map is None: 2116 return 2117 if start_map > 0: 2118 before = self._cmap[start_map - 1] 2119 idx = start_map - 1 2120 before = start - 1 2121 x = start 2122 for juska in self._cmap[start_map:]: 2123 idx += 1 2124 if idx in self._indexes["_cmap"]: 2125 column = self._indexes["_cmap"][idx] 2126 else: 2127 column = self._get_element_idx2(_xpath_column_idx, idx) 2128 self._indexes["_cmap"][idx] = column 2129 repeated = juska - before 2130 before = juska 2131 for _i in range(repeated or 1): 2132 if x <= end: 2133 column = column.clone 2134 column.x = x 2135 x += 1 2136 if repeated > 1 or (x == start and start > 0): 2137 column.repeated = None 2138 yield column 2139 2140 def get_columns( 2141 self, 2142 coord: tuple | list | str | None = None, 2143 style: str | None = None, 2144 ) -> list[Column]: 2145 """Get the list of columns matching the criteria. Each result is a 2146 tuple of (x, column). 2147 2148 Arguments: 2149 2150 coord -- str or tuple of int : coordinates of columns 2151 2152 style -- str 2153 2154 Return: list of columns 2155 """ 2156 if coord: 2157 x, _y, _z, t = self._translate_column_coordinates(coord) 2158 else: 2159 x = t = None 2160 if not style: 2161 return list(self.traverse_columns(start=x, end=t)) 2162 columns = [] 2163 for column in self.traverse_columns(start=x, end=t): 2164 if style != column.style: 2165 continue 2166 columns.append(column) 2167 return columns 2168 2169 def _get_column2(self, x: int) -> Column | None: 2170 # Outside the defined table 2171 if x >= self.width: 2172 return Column() 2173 # Inside the defined table 2174 odf_idx = find_odf_idx(self._cmap, x) 2175 if odf_idx is not None: 2176 column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2177 if column is None: 2178 return None 2179 # fixme : no clone here => change doc and unit tests 2180 return column.clone # type: ignore 2181 # return row 2182 return None 2183 2184 def get_column(self, x: int | str) -> Column: 2185 """Get the column at the given "x" position. 2186 2187 ODF columns don't contain cells, only style information. 2188 2189 Position start at 0. So cell C4 is on column 2. Alphabetical position 2190 like "C" is accepted. 2191 2192 A copy is returned, use set_column() to push it back. 2193 2194 Arguments: 2195 2196 x -- int or str 2197 2198 Return: Column 2199 """ 2200 x = self._translate_x_from_any(x) 2201 column = self._get_column2(x) 2202 if column is None: 2203 raise ValueError 2204 column.x = x 2205 return column 2206 2207 def set_column( 2208 self, 2209 x: int | str, 2210 column: Column | None = None, 2211 ) -> Column: 2212 """Replace the column at the given "x" position. 2213 2214 ODF columns don't contain cells, only style information. 2215 2216 Position start at 0. So cell C4 is on column 2. Alphabetical position 2217 like "C" is accepted. 2218 2219 Arguments: 2220 2221 x -- int or str 2222 2223 column -- Column 2224 """ 2225 x = self._translate_x_from_any(x) 2226 if column is None: 2227 column = Column() 2228 repeated = 1 2229 else: 2230 repeated = column.repeated or 1 2231 column.x = x 2232 # Outside the defined table ? 2233 diff = x - self.width 2234 if diff == 0: 2235 column_back = self.append_column(column, _repeated=repeated) 2236 elif diff > 0: 2237 self.append_column(Column(repeated=diff), _repeated=diff) 2238 column_back = self.append_column(column, _repeated=repeated) 2239 else: 2240 # Inside the defined table 2241 column_back = set_item_in_vault( # type: ignore 2242 x, column, self, _xpath_column_idx, "_cmap" 2243 ) 2244 return column_back 2245 2246 def insert_column( 2247 self, 2248 x: int | str, 2249 column: Column | None = None, 2250 ) -> Column: 2251 """Insert the column before the given "x" position. If no column is 2252 given, an empty one is created. 2253 2254 ODF columns don't contain cells, only style information. 2255 2256 Position start at 0. So cell C4 is on column 2. Alphabetical position 2257 like "C" is accepted. 2258 2259 Arguments: 2260 2261 x -- int or str 2262 2263 column -- Column 2264 """ 2265 if column is None: 2266 column = Column() 2267 x = self._translate_x_from_any(x) 2268 diff = x - self.width 2269 if diff < 0: 2270 column_back = insert_item_in_vault( 2271 x, column, self, _xpath_column_idx, "_cmap" 2272 ) 2273 elif diff == 0: 2274 column_back = self.append_column(column.clone) 2275 else: 2276 self.append_column(Column(repeated=diff), _repeated=diff) 2277 column_back = self.append_column(column.clone) 2278 column_back.x = x # type: ignore 2279 # Repetitions are accepted 2280 repeated = column.repeated or 1 2281 # Update width on every row 2282 for row in self._get_rows(): 2283 if row.width > x: 2284 row.insert_cell(x, Cell(repeated=repeated)) 2285 # Shorter rows don't need insert 2286 # Longer rows shouldn't exist! 2287 return column_back # type: ignore 2288 2289 def append_column( 2290 self, 2291 column: Column | None = None, 2292 _repeated: int | None = None, 2293 ) -> Column: 2294 """Append the column at the end of the table. If no column is given, 2295 an empty one is created. 2296 2297 ODF columns don't contain cells, only style information. 2298 2299 Position start at 0. So cell C4 is on column 2. Alphabetical position 2300 like "C" is accepted. 2301 2302 Arguments: 2303 2304 column -- Column 2305 """ 2306 if column is None: 2307 column = Column() 2308 else: 2309 column = column.clone 2310 if not self._cmap: 2311 position = 0 2312 else: 2313 odf_idx = len(self._cmap) - 1 2314 last_column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2315 if last_column is None: 2316 raise ValueError 2317 position = self.index(last_column) + 1 2318 column.x = self.width 2319 self.insert(column, position=position) 2320 # Repetitions are accepted 2321 if _repeated is None: 2322 _repeated = column.repeated or 1 2323 self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated) 2324 # No need to update row widths 2325 return column 2326 2327 def delete_column(self, x: int | str) -> None: 2328 """Delete the column at the given position. ODF columns don't contain 2329 cells, only style information. 2330 2331 Position start at 0. So cell C4 is on column 2. Alphabetical position 2332 like "C" is accepted. 2333 2334 Arguments: 2335 2336 x -- int or str 2337 """ 2338 x = self._translate_x_from_any(x) 2339 # Outside the defined table 2340 if x >= self.width: 2341 return 2342 # Inside the defined table 2343 delete_item_in_vault(x, self, _xpath_column_idx, "_cmap") 2344 # Update width 2345 width = self.width 2346 for row in self._get_rows(): 2347 if row.width >= width: 2348 row.delete_cell(x) 2349 2350 def get_column_cells( # noqa: C901 2351 self, 2352 x: int | str, 2353 style: str | None = None, 2354 content: str | None = None, 2355 cell_type: str | None = None, 2356 complete: bool = False, 2357 ) -> list[Cell | None]: 2358 """Get the list of cells at the given position. 2359 2360 Position start at 0. So cell C4 is on column 2. Alphabetical position 2361 like "C" is accepted. 2362 2363 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2364 type, aka non empty cells. 2365 2366 If complete is True, replace missing values by None. 2367 2368 Arguments: 2369 2370 x -- int or str 2371 2372 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2373 'currency', 'percentage' or 'all' 2374 2375 content -- str regex 2376 2377 style -- str 2378 2379 complete -- boolean 2380 2381 Return: list of Cell 2382 """ 2383 x = self._translate_x_from_any(x) 2384 if cell_type: 2385 cell_type = cell_type.lower().strip() 2386 cells: list[Cell | None] = [] 2387 if not style and not content and not cell_type: 2388 for row in self.traverse(): 2389 cells.append(row.get_cell(x, clone=True)) 2390 return cells 2391 for row in self.traverse(): 2392 cell = row.get_cell(x, clone=True) 2393 if cell is None: 2394 raise ValueError 2395 # Filter the cells by cell_type 2396 if cell_type: 2397 ctype = cell.type 2398 if not ctype or not (ctype == cell_type or cell_type == "all"): 2399 if complete: 2400 cells.append(None) 2401 continue 2402 # Filter the cells with the regex 2403 if content and not cell.match(content): 2404 if complete: 2405 cells.append(None) 2406 continue 2407 # Filter the cells with the style 2408 if style and style != cell.style: 2409 if complete: 2410 cells.append(None) 2411 continue 2412 cells.append(cell) 2413 return cells 2414 2415 def get_column_values( 2416 self, 2417 x: int | str, 2418 cell_type: str | None = None, 2419 complete: bool = True, 2420 get_type: bool = False, 2421 ) -> list[Any]: 2422 """Shortcut to get the list of Python values for the cells at the 2423 given position. 2424 2425 Position start at 0. So cell C4 is on column 2. Alphabetical position 2426 like "C" is accepted. 2427 2428 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2429 type, aka non empty cells. 2430 If cell_type and complete is True, replace missing values by None. 2431 2432 If get_type is True, returns a tuple (value, ODF type of value) 2433 2434 Arguments: 2435 2436 x -- int or str 2437 2438 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2439 'currency', 'percentage' or 'all' 2440 2441 complete -- boolean 2442 2443 get_type -- boolean 2444 2445 Return: list of Python types 2446 """ 2447 cells = self.get_column_cells( 2448 x, style=None, content=None, cell_type=cell_type, complete=complete 2449 ) 2450 values: list[Any] = [] 2451 for cell in cells: 2452 if cell is None: 2453 if complete: 2454 if get_type: 2455 values.append((None, None)) 2456 else: 2457 values.append(None) 2458 continue 2459 if cell_type: 2460 ctype = cell.type 2461 if not ctype or not (ctype == cell_type or cell_type == "all"): 2462 if complete: 2463 if get_type: 2464 values.append((None, None)) 2465 else: 2466 values.append(None) 2467 continue 2468 values.append(cell.get_value(get_type=get_type)) 2469 return values 2470 2471 def set_column_cells(self, x: int | str, cells: list[Cell]) -> None: 2472 """Shortcut to set the list of cells at the given position. 2473 2474 Position start at 0. So cell C4 is on column 2. Alphabetical position 2475 like "C" is accepted. 2476 2477 The list must have the same length than the table height. 2478 2479 Arguments: 2480 2481 x -- int or str 2482 2483 cells -- list of Cell 2484 """ 2485 height = self.height 2486 if len(cells) != height: 2487 raise ValueError(f"col mismatch: {height} cells expected") 2488 cells_iterator = iter(cells) 2489 for y, row in enumerate(self.traverse()): 2490 row.set_cell(x, next(cells_iterator)) 2491 self.set_row(y, row) 2492 2493 def set_column_values( 2494 self, 2495 x: int | str, 2496 values: list, 2497 cell_type: str | None = None, 2498 currency: str | None = None, 2499 style: str | None = None, 2500 ) -> None: 2501 """Shortcut to set the list of Python values of cells at the given 2502 position. 2503 2504 Position start at 0. So cell C4 is on column 2. Alphabetical position 2505 like "C" is accepted. 2506 2507 The list must have the same length than the table height. 2508 2509 Arguments: 2510 2511 x -- int or str 2512 2513 values -- list of Python types 2514 2515 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 2516 'string' or 'time' 2517 2518 currency -- three-letter str 2519 2520 style -- str 2521 """ 2522 cells = [ 2523 Cell(value, cell_type=cell_type, currency=currency, style=style) 2524 for value in values 2525 ] 2526 self.set_column_cells(x, cells) 2527 2528 def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool: 2529 """Return wether every cell in the column at "x" position has no value 2530 or the value evaluates to False (empty string), and no style. 2531 2532 Position start at 0. So cell C4 is on column 2. Alphabetical position 2533 like "C" is accepted. 2534 2535 If aggressive is True, empty cells with style are considered empty. 2536 2537 Return: bool 2538 """ 2539 for cell in self.get_column_cells(x): 2540 if cell is None: 2541 continue 2542 if not cell.is_empty(aggressive=aggressive): 2543 return False 2544 return True 2545 2546 # Named Range 2547 2548 def get_named_ranges( # type: ignore 2549 self, 2550 table_name: str | list[str] | None = None, 2551 ) -> list[NamedRange]: 2552 """Returns the list of available Name Ranges of the spreadsheet. If 2553 table_name is provided, limits the search to these tables. 2554 Beware : named ranges are stored at the body level, thus do not call 2555 this method on a cloned table. 2556 2557 Arguments: 2558 2559 table_names -- str or list of str, names of tables 2560 2561 Return : list of table_range 2562 """ 2563 body = self.document_body 2564 if not body: 2565 return [] 2566 all_named_ranges = body.get_named_ranges() 2567 if not table_name: 2568 return all_named_ranges # type:ignore 2569 filter_ = [] 2570 if isinstance(table_name, str): 2571 filter_.append(table_name) 2572 elif isiterable(table_name): 2573 filter_.extend(table_name) 2574 else: 2575 raise ValueError( 2576 f"table_name must be string or Iterable, not {type(table_name)}" 2577 ) 2578 return [ 2579 nr for nr in all_named_ranges if nr.table_name in filter_ # type:ignore 2580 ] 2581 2582 def get_named_range(self, name: str) -> NamedRange: 2583 """Returns the Name Ranges of the specified name. If 2584 table_name is provided, limits the search to these tables. 2585 Beware : named ranges are stored at the body level, thus do not call 2586 this method on a cloned table. 2587 2588 Arguments: 2589 2590 name -- str, name of the named range object 2591 2592 Return : NamedRange 2593 """ 2594 body = self.document_body 2595 if not body: 2596 raise ValueError("Table is not inside a document") 2597 return body.get_named_range(name) # type: ignore 2598 2599 def set_named_range( 2600 self, 2601 name: str, 2602 crange: str | tuple | list, 2603 table_name: str | None = None, 2604 usage: str | None = None, 2605 ) -> None: 2606 """Create a Named Range element and insert it in the document. 2607 Beware : named ranges are stored at the body level, thus do not call 2608 this method on a cloned table. 2609 2610 Arguments: 2611 2612 name -- str, name of the named range 2613 2614 crange -- str or tuple of int, cell or area coordinate 2615 2616 table_name -- str, name of the table 2617 2618 uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2619 """ 2620 body = self.document_body 2621 if not body: 2622 raise ValueError("Table is not inside a document") 2623 if not name: 2624 raise ValueError("Name required.") 2625 if table_name is None: 2626 table_name = self.name 2627 named_range = NamedRange(name, crange, table_name, usage) 2628 body.append_named_range(named_range) 2629 2630 def delete_named_range(self, name: str) -> None: 2631 """Delete the Named Range of specified name from the spreadsheet. 2632 Beware : named ranges are stored at the body level, thus do not call 2633 this method on a cloned table. 2634 2635 Arguments: 2636 2637 name -- str 2638 """ 2639 name = name.strip() 2640 if not name: 2641 raise ValueError("Name required.") 2642 body = self.document_body 2643 if not body: 2644 raise ValueError("Table is not inside a document.") 2645 body.delete_named_range(name) 2646 2647 # 2648 # Cell span 2649 # 2650 2651 def set_span( # noqa: C901 2652 self, 2653 area: str | tuple | list, 2654 merge: bool = False, 2655 ) -> bool: 2656 """Create a Cell Span : span the first cell of the area on several 2657 columns and/or rows. 2658 If merge is True, replace text of the cell by the concatenation of 2659 existing text in covered cells. 2660 Beware : if merge is True, old text is changed, if merge is False 2661 (the default), old text in coverd cells is still present but not 2662 displayed by most GUI. 2663 2664 If the area defines only one cell, the set span will do nothing. 2665 It is not allowed to apply set span to an area whose one cell already 2666 belongs to previous cell span. 2667 2668 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2669 be provided as an alpha numeric value like "A1:B2' or a tuple like 2670 (0, 0, 1, 1) or (0, 0). 2671 2672 Arguments: 2673 2674 area -- str or tuple of int, cell or area coordinate 2675 2676 merge -- boolean 2677 """ 2678 # get area 2679 digits = convert_coordinates(area) 2680 if len(digits) == 4: 2681 x, y, z, t = digits 2682 else: 2683 x, y = digits 2684 z, t = digits 2685 start = x, y 2686 end = z, t 2687 if start == end: 2688 # one cell : do nothing 2689 return False 2690 if x is None: 2691 raise ValueError 2692 if y is None: 2693 raise ValueError 2694 if z is None: 2695 raise ValueError 2696 if t is None: 2697 raise ValueError 2698 # check for previous span 2699 good = True 2700 # Check boundaries and empty cells : need to crate non existent cells 2701 # so don't use get_cells directly, but get_cell 2702 cells = [] 2703 for yy in range(y, t + 1): 2704 row_cells = [] 2705 for xx in range(x, z + 1): 2706 row_cells.append( 2707 self.get_cell((xx, yy), clone=True, keep_repeated=False) 2708 ) 2709 cells.append(row_cells) 2710 for row in cells: 2711 for cell in row: 2712 if cell._is_spanned(): 2713 good = False 2714 break 2715 if not good: 2716 break 2717 if not good: 2718 return False 2719 # Check boundaries 2720 # if z >= self.width or t >= self.height: 2721 # self.set_cell(coord = end) 2722 # print area, z, t 2723 # cells = self.get_cells((x, y, z, t)) 2724 # print cells 2725 # do it: 2726 if merge: 2727 val_list = [] 2728 for row in cells: 2729 for cell in row: 2730 if cell.is_empty(aggressive=True): 2731 continue 2732 val = cell.get_value() 2733 if val is not None: 2734 if isinstance(val, str): 2735 val.strip() 2736 if val != "": 2737 val_list.append(val) 2738 cell.clear() 2739 if val_list: 2740 if len(val_list) == 1: 2741 cells[0][0].set_value(val_list[0]) 2742 else: 2743 value = " ".join([str(v) for v in val_list if v]) 2744 cells[0][0].set_value(value) 2745 cols = z - x + 1 2746 cells[0][0].set_attribute("table:number-columns-spanned", str(cols)) 2747 rows = t - y + 1 2748 cells[0][0].set_attribute("table:number-rows-spanned", str(rows)) 2749 for cell in cells[0][1:]: 2750 cell.tag = "table:covered-table-cell" 2751 for row in cells[1:]: 2752 for cell in row: 2753 cell.tag = "table:covered-table-cell" 2754 # replace cells in table 2755 self.set_cells(cells, coord=start, clone=False) 2756 return True 2757 2758 def del_span(self, area: str | tuple | list) -> bool: 2759 """Delete a Cell Span. 'area' is the cell coordiante of the upper left 2760 cell of the spanned area. 2761 2762 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2763 be provided as an alpha numeric value like "A1:B2' or a tuple like 2764 (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell 2765 is used. 2766 2767 Arguments: 2768 2769 area -- str or tuple of int, cell or area coordinate 2770 """ 2771 # get area 2772 digits = convert_coordinates(area) 2773 if len(digits) == 4: 2774 x, y, _z, _t = digits 2775 else: 2776 x, y = digits 2777 if x is None: 2778 raise ValueError 2779 if y is None: 2780 raise ValueError 2781 start = x, y 2782 # check for previous span 2783 cell0 = self.get_cell(start) 2784 nb_cols = cell0.get_attribute_integer("table:number-columns-spanned") 2785 if nb_cols is None: 2786 return False 2787 nb_rows = cell0.get_attribute_integer("table:number-rows-spanned") 2788 if nb_rows is None: 2789 return False 2790 z = x + nb_cols - 1 2791 t = y + nb_rows - 1 2792 cells = self.get_cells((x, y, z, t)) 2793 cells[0][0].del_attribute("table:number-columns-spanned") 2794 cells[0][0].del_attribute("table:number-rows-spanned") 2795 for cell in cells[0][1:]: 2796 cell.tag = "table:table-cell" 2797 for row in cells[1:]: 2798 for cell in row: 2799 cell.tag = "table:table-cell" 2800 # replace cells in table 2801 self.set_cells(cells, coord=start, clone=False) 2802 return True 2803 2804 # Utilities 2805 2806 def to_csv( 2807 self, 2808 path_or_file: str | Path | None = None, 2809 dialect: str = "excel", 2810 ) -> Any: 2811 """Write the table as CSV in the file. 2812 2813 If the file is a string, it is opened as a local path. Else an 2814 opened file-like is expected. 2815 2816 Arguments: 2817 2818 path_or_file -- str or file-like 2819 2820 dialect -- str, python csv.dialect, can be 'excel', 'unix'... 2821 """ 2822 2823 def write_content(csv_writer: object) -> None: 2824 for values in self.iter_values(): 2825 line = [] 2826 for value in values: 2827 if value is None: 2828 value = "" 2829 if isinstance(value, str): 2830 value = value.strip() 2831 line.append(value) 2832 csv_writer.writerow(line) # type: ignore 2833 2834 out = StringIO(newline="") 2835 csv_writer = csv.writer(out, dialect=dialect) 2836 write_content(csv_writer) 2837 if path_or_file is None: 2838 return out.getvalue() 2839 path = Path(path_or_file) 2840 path.write_text(out.getvalue()) 2841 return None
ODF table "table:table"
291 def __init__( 292 self, 293 name: str | None = None, 294 width: int | None = None, 295 height: int | None = None, 296 protected: bool = False, 297 protection_key: str | None = None, 298 display: bool = True, 299 printable: bool = True, 300 print_ranges: list[str] | None = None, 301 style: str | None = None, 302 **kwargs: Any, 303 ) -> None: 304 """Create a table element, optionally prefilled with "height" rows of 305 "width" cells each. 306 307 If the table is to be protected, a protection key must be provided, 308 i.e. a hash value of the password. 309 310 If the table must not be displayed, set "display" to False. 311 312 If the table must not be printed, set "printable" to False. The table 313 will not be printed when it is not displayed, whatever the value of 314 this argument. 315 316 Ranges of cells to print can be provided as a list of cell ranges, 317 e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. 318 "E6:K12 P6:R12". 319 320 You can access and modify the XML tree manually, but you probably want 321 to use the API to access and alter cells. It will save you from 322 handling repetitions and the same number of cells for each row. 323 324 If you use both the table API and the XML API, you are on your own for 325 ensuiring model integrity. 326 327 Arguments: 328 329 name -- str 330 331 width -- int 332 333 height -- int 334 335 protected -- bool 336 337 protection_key -- str 338 339 display -- bool 340 341 printable -- bool 342 343 print_ranges -- list 344 345 style -- str 346 """ 347 super().__init__(**kwargs) 348 self._indexes = {} 349 self._indexes["_cmap"] = {} 350 self._indexes["_tmap"] = {} 351 if self._do_init: 352 self.name = name 353 if protected: 354 self.protected = protected 355 self.set_protection_key = protection_key 356 if not display: 357 self.displayed = display 358 if not printable: 359 self.printable = printable 360 if print_ranges: 361 self.print_ranges = print_ranges 362 if style: 363 self.style = style 364 # Prefill the table 365 if width is not None or height is not None: 366 width = width or 1 367 height = height or 1 368 # Column groups for style information 369 columns = Column(repeated=width) 370 self._append(columns) 371 for _i in range(height): 372 row = Row(width) 373 self._append(row) 374 self._compute_table_cache()
Create a table element, optionally prefilled with "height" rows of "width" cells each.
If the table is to be protected, a protection key must be provided, i.e. a hash value of the password.
If the table must not be displayed, set "display" to False.
If the table must not be printed, set "printable" to False. The table will not be printed when it is not displayed, whatever the value of this argument.
Ranges of cells to print can be provided as a list of cell ranges, e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. "E6:K12 P6:R12".
You can access and modify the XML tree manually, but you probably want to use the API to access and alter cells. It will save you from handling repetitions and the same number of cells for each row.
If you use both the table API and the XML API, you are on your own for ensuiring model integrity.
Arguments:
name -- str
width -- int
height -- int
protected -- bool
protection_key -- str
display -- bool
printable -- bool
print_ranges -- list
style -- str
731 def append(self, something: Element | str) -> None: 732 """Dispatch .append() call to append_row() or append_column().""" 733 if isinstance(something, Row): 734 self.append_row(something) 735 elif isinstance(something, Column): 736 self.append_column(something) 737 else: 738 # probably still an error 739 self._append(something)
Dispatch .append() call to append_row() or append_column().
741 @property 742 def height(self) -> int: 743 """Get the current height of the table. 744 745 Return: int 746 """ 747 try: 748 height = self._tmap[-1] + 1 749 except Exception: 750 height = 0 751 return height
Get the current height of the table.
Return: int
753 @property 754 def width(self) -> int: 755 """Get the current width of the table, measured on columns. 756 757 Rows may have different widths, use the Table API to ensure width 758 consistency. 759 760 Return: int 761 """ 762 # Columns are our reference for user expected width 763 764 try: 765 width = self._cmap[-1] + 1 766 except Exception: 767 width = 0 768 769 # columns = self._get_columns() 770 # repeated = self.xpath( 771 # 'table:table-column/@table:number-columns-repeated') 772 # unrepeated = len(columns) - len(repeated) 773 # ws = sum(int(r) for r in repeated) + unrepeated 774 # if w != ws: 775 # print "WARNING ws", ws, "w", w 776 777 return width
Get the current width of the table, measured on columns.
Rows may have different widths, use the Table API to ensure width consistency.
Return: int
779 @property 780 def size(self) -> tuple[int, int]: 781 """Shortcut to get the current width and height of the table. 782 783 Return: (int, int) 784 """ 785 return self.width, self.height
Shortcut to get the current width and height of the table.
Return: (int, int)
787 @property 788 def name(self) -> str | None: 789 """Get / set the name of the table.""" 790 return self.get_attribute_string("table:name")
Get / set the name of the table.
851 @property 852 def style(self) -> str | None: 853 """Get / set the style of the table 854 855 Return: str 856 """ 857 return self.get_attribute_string("table:style-name")
Get / set the style of the table
Return: str
863 def get_formatted_text(self, context: dict | None = None) -> str: 864 if context and context["rst_mode"]: 865 return self._get_formatted_text_rst(context) 866 return self._get_formatted_text_normal(context)
This function should return a beautiful version of the text.
868 def get_values( 869 self, 870 coord: tuple | list | str | None = None, 871 cell_type: str | None = None, 872 complete: bool = True, 873 get_type: bool = False, 874 flat: bool = False, 875 ) -> list: 876 """Get a matrix of values of the table. 877 878 Filter by coordinates will parse the area defined by the coordinates. 879 880 If 'cell_type' is used and 'complete' is True (default), missing values 881 are replaced by None. 882 Filter by ' cell_type = "all" ' will retrieve cells of any 883 type, aka non empty cells. 884 885 If 'cell_type' is None, complete is always True : with no cell type 886 queried, get_values() returns None for each empty cell, the length 887 each lists is equal to the width of the table. 888 889 If get_type is True, returns tuples (value, ODF type of value), or 890 (None, None) for empty cells with complete True. 891 892 If flat is True, the methods return a single list of all the values. 893 By default, flat is False. 894 895 Arguments: 896 897 coord -- str or tuple of int : coordinates of area 898 899 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 900 'currency', 'percentage' or 'all' 901 902 complete -- boolean 903 904 get_type -- boolean 905 906 Return: list of lists of Python types 907 """ 908 if coord: 909 x, y, z, t = self._translate_table_coordinates(coord) 910 else: 911 x = y = z = t = None 912 data = [] 913 for row in self.traverse(start=y, end=t): 914 if z is None: 915 width = self.width 916 else: 917 width = min(z + 1, self.width) 918 if x is not None: 919 width -= x 920 values = row.get_values( 921 (x, z), 922 cell_type=cell_type, 923 complete=complete, 924 get_type=get_type, 925 ) 926 # complete row to match request width 927 if complete: 928 if get_type: 929 values.extend([(None, None)] * (width - len(values))) 930 else: 931 values.extend([None] * (width - len(values))) 932 if flat: 933 data.extend(values) 934 else: 935 data.append(values) 936 return data
Get a matrix of values of the table.
Filter by coordinates will parse the area defined by the coordinates.
If 'cell_type' is used and 'complete' is True (default), missing values are replaced by None. Filter by ' cell_type = "all" ' will retrieve cells of any type, aka non empty cells.
If 'cell_type' is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length each lists is equal to the width of the table.
If get_type is True, returns tuples (value, ODF type of value), or (None, None) for empty cells with complete True.
If flat is True, the methods return a single list of all the values. By default, flat is False.
Arguments:
coord -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of lists of Python types
938 def iter_values( 939 self, 940 coord: tuple | list | str | None = None, 941 cell_type: str | None = None, 942 complete: bool = True, 943 get_type: bool = False, 944 ) -> Iterator[list]: 945 """Iterate through lines of Python values of the table. 946 947 Filter by coordinates will parse the area defined by the coordinates. 948 949 cell_type, complete, grt_type : see get_values() 950 951 952 953 Arguments: 954 955 coord -- str or tuple of int : coordinates of area 956 957 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 958 'currency', 'percentage' or 'all' 959 960 complete -- boolean 961 962 get_type -- boolean 963 964 Return: iterator of lists 965 """ 966 if coord: 967 x, y, z, t = self._translate_table_coordinates(coord) 968 else: 969 x = y = z = t = None 970 for row in self.traverse(start=y, end=t): 971 if z is None: 972 width = self.width 973 else: 974 width = min(z + 1, self.width) 975 if x is not None: 976 width -= x 977 values = row.get_values( 978 (x, z), 979 cell_type=cell_type, 980 complete=complete, 981 get_type=get_type, 982 ) 983 # complete row to match column width 984 if complete: 985 if get_type: 986 values.extend([(None, None)] * (width - len(values))) 987 else: 988 values.extend([None] * (width - len(values))) 989 yield values
Iterate through lines of Python values of the table.
Filter by coordinates will parse the area defined by the coordinates.
cell_type, complete, grt_type : see get_values()
Arguments:
coord -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: iterator of lists
991 def set_values( 992 self, 993 values: list, 994 coord: tuple | list | str | None = None, 995 style: str | None = None, 996 cell_type: str | None = None, 997 currency: str | None = None, 998 ) -> None: 999 """Set the value of cells in the table, from the 'coord' position 1000 with values. 1001 1002 'coord' is the coordinate of the upper left cell to be modified by 1003 values. If 'coord' is None, default to the position (0,0) ("A1"). 1004 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1005 area is used as coordinate. 1006 1007 The table is *not* cleared before the operation, to reset the table 1008 before setting values, use table.clear(). 1009 1010 A list of lists is expected, with as many lists as rows, and as many 1011 items in each sublist as cells to be setted. None values in the list 1012 will create empty cells with no cell type (but eventually a style). 1013 1014 Arguments: 1015 1016 coord -- tuple or str 1017 1018 values -- list of lists of python types 1019 1020 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1021 'string' or 'time' 1022 1023 currency -- three-letter str 1024 1025 style -- str 1026 """ 1027 if coord: 1028 x, y = self._translate_cell_coordinates(coord) 1029 else: 1030 x = y = 0 1031 if y is None: 1032 y = 0 1033 if x is None: 1034 x = 0 1035 y -= 1 1036 for row_values in values: 1037 y += 1 1038 if not row_values: 1039 continue 1040 row = self.get_row(y, clone=True) 1041 repeated = row.repeated or 1 1042 if repeated >= 2: 1043 row.repeated = None 1044 row.set_values( 1045 row_values, 1046 start=x, 1047 cell_type=cell_type, 1048 currency=currency, 1049 style=style, 1050 ) 1051 self.set_row(y, row, clone=False) 1052 self._update_width(row)
Set the value of cells in the table, from the 'coord' position with values.
'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.
The table is not cleared before the operation, to reset the table before setting values, use table.clear().
A list of lists is expected, with as many lists as rows, and as many items in each sublist as cells to be setted. None values in the list will create empty cells with no cell type (but eventually a style).
Arguments:
coord -- tuple or str
values -- list of lists of python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
1054 def rstrip(self, aggressive: bool = False) -> None: 1055 """Remove *in-place* empty rows below and empty cells at the right of 1056 the table. Cells are empty if they contain no value or it evaluates 1057 to False, and no style. 1058 1059 If aggressive is True, empty cells with style are removed too. 1060 1061 Argument: 1062 1063 aggressive -- bool 1064 """ 1065 # Step 1: remove empty rows below the table 1066 for row in reversed(self._get_rows()): 1067 if row.is_empty(aggressive=aggressive): 1068 row.parent.delete(row) # type: ignore 1069 else: 1070 break 1071 # Step 2: rstrip remaining rows 1072 max_width = 0 1073 for row in self._get_rows(): 1074 row.rstrip(aggressive=aggressive) 1075 # keep count of the biggest row 1076 max_width = max(max_width, row.width) 1077 # raz cache of rows 1078 self._indexes["_tmap"] = {} 1079 # Step 3: trim columns to match max_width 1080 columns = self._get_columns() 1081 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1082 if not isinstance(repeated_cols, list): 1083 raise TypeError 1084 unrepeated = len(columns) - len(repeated_cols) 1085 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1086 diff = column_width - max_width 1087 if diff > 0: 1088 for column in reversed(columns): 1089 repeated = column.repeated or 1 1090 repeated = repeated - diff 1091 if repeated > 0: 1092 column.repeated = repeated 1093 break 1094 else: 1095 column.parent.delete(column) 1096 diff = -repeated 1097 if diff == 0: 1098 break 1099 # raz cache of columns 1100 self._indexes["_cmap"] = {} 1101 self._compute_table_cache()
Remove in-place empty rows below and empty cells at the right of the table. Cells are empty if they contain no value or it evaluates to False, and no style.
If aggressive is True, empty cells with style are removed too.
Argument:
aggressive -- bool
1103 def transpose(self, coord: tuple | list | str | None = None) -> None: # noqa: C901 1104 """Swap *in-place* rows and columns of the table. 1105 1106 If 'coord' is not None, apply transpose only to the area defined by the 1107 coordinates. Beware, if area is not square, some cells mays be over 1108 written during the process. 1109 1110 Arguments: 1111 1112 coord -- str or tuple of int : coordinates of area 1113 1114 start -- int or str 1115 """ 1116 data = [] 1117 if coord is None: 1118 for row in self.traverse(): 1119 data.append(list(row.traverse())) 1120 transposed_data = zip_longest(*data) 1121 self.clear() 1122 # new_rows = [] 1123 for row_cells in transposed_data: 1124 if not isiterable(row_cells): 1125 row_cells = (row_cells,) 1126 row = Row() 1127 row.extend_cells(row_cells) 1128 self.append_row(row, clone=False) 1129 self._compute_table_cache() 1130 else: 1131 x, y, z, t = self._translate_table_coordinates(coord) 1132 if x is None: 1133 x = 0 1134 else: 1135 x = min(x, self.width - 1) 1136 if z is None: 1137 z = self.width - 1 1138 else: 1139 z = min(z, self.width - 1) 1140 if y is None: 1141 y = 0 1142 else: 1143 y = min(y, self.height - 1) 1144 if t is None: 1145 t = self.height - 1 1146 else: 1147 t = min(t, self.height - 1) 1148 for row in self.traverse(start=y, end=t): 1149 data.append(list(row.traverse(start=x, end=z))) 1150 transposed_data = zip_longest(*data) 1151 # clear locally 1152 w = z - x + 1 1153 h = t - y + 1 1154 if w != h: 1155 nones = [[None] * w for i in range(h)] 1156 self.set_values(nones, coord=(x, y, z, t)) 1157 # put transposed 1158 filtered_data: list[tuple[Cell]] = [] 1159 for row_cells in transposed_data: 1160 if isinstance(row_cells, (list, tuple)): 1161 filtered_data.append(row_cells) 1162 else: 1163 filtered_data.append((row_cells,)) 1164 self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1)) 1165 self._compute_table_cache()
Swap in-place rows and columns of the table.
If 'coord' is not None, apply transpose only to the area defined by the coordinates. Beware, if area is not square, some cells mays be over written during the process.
Arguments:
coord -- str or tuple of int : coordinates of area
start -- int or str
1167 def is_empty(self, aggressive: bool = False) -> bool: 1168 """Return whether every cell in the table has no value or the value 1169 evaluates to False (empty string), and no style. 1170 1171 If aggressive is True, empty cells with style are considered empty. 1172 1173 Arguments: 1174 1175 aggressive -- bool 1176 """ 1177 return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())
Return whether every cell in the table has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
1186 def traverse( # noqa: C901 1187 self, 1188 start: int | None = None, 1189 end: int | None = None, 1190 ) -> Iterator[Row]: 1191 """Yield as many row elements as expected rows in the table, i.e. 1192 expand repetitions by returning the same row as many times as 1193 necessary. 1194 1195 Arguments: 1196 1197 start -- int 1198 1199 end -- int 1200 1201 Copies are returned, use set_row() to push them back. 1202 """ 1203 idx = -1 1204 before = -1 1205 y = 0 1206 if start is None and end is None: 1207 for juska in self._tmap: 1208 idx += 1 1209 if idx in self._indexes["_tmap"]: 1210 row = self._indexes["_tmap"][idx] 1211 else: 1212 row = self._get_element_idx2(_xpath_row_idx, idx) 1213 self._indexes["_tmap"][idx] = row 1214 repeated = juska - before 1215 before = juska 1216 for _i in range(repeated or 1): 1217 # Return a copy without the now obsolete repetition 1218 row = row.clone 1219 row.y = y 1220 y += 1 1221 if repeated > 1: 1222 row.repeated = None 1223 yield row 1224 else: 1225 if start is None: 1226 start = 0 1227 start = max(0, start) 1228 if end is None: 1229 try: 1230 end = self._tmap[-1] 1231 except Exception: 1232 end = -1 1233 start_map = find_odf_idx(self._tmap, start) 1234 if start_map is None: 1235 return 1236 if start_map > 0: 1237 before = self._tmap[start_map - 1] 1238 idx = start_map - 1 1239 before = start - 1 1240 y = start 1241 for juska in self._tmap[start_map:]: 1242 idx += 1 1243 if idx in self._indexes["_tmap"]: 1244 row = self._indexes["_tmap"][idx] 1245 else: 1246 row = self._get_element_idx2(_xpath_row_idx, idx) 1247 self._indexes["_tmap"][idx] = row 1248 repeated = juska - before 1249 before = juska 1250 for _i in range(repeated or 1): 1251 if y <= end: 1252 row = row.clone 1253 row.y = y 1254 y += 1 1255 if repeated > 1 or (y == start and start > 0): 1256 row.repeated = None 1257 yield row
Yield as many row elements as expected rows in the table, i.e. expand repetitions by returning the same row as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_row() to push them back.
1259 def get_rows( 1260 self, 1261 coord: tuple | list | str | None = None, 1262 style: str | None = None, 1263 content: str | None = None, 1264 ) -> list[Row]: 1265 """Get the list of rows matching the criteria. 1266 1267 Filter by coordinates will parse the area defined by the coordinates. 1268 1269 Arguments: 1270 1271 coord -- str or tuple of int : coordinates of rows 1272 1273 content -- str regex 1274 1275 style -- str 1276 1277 Return: list of rows 1278 """ 1279 if coord: 1280 _x, y, _z, t = self._translate_table_coordinates(coord) 1281 else: 1282 y = t = None 1283 # fixme : not clones ? 1284 if not content and not style: 1285 return list(self.traverse(start=y, end=t)) 1286 rows = [] 1287 for row in self.traverse(start=y, end=t): 1288 if content and not row.match(content): 1289 continue 1290 if style and style != row.style: 1291 continue 1292 rows.append(row) 1293 return rows
Get the list of rows matching the criteria.
Filter by coordinates will parse the area defined by the coordinates.
Arguments:
coord -- str or tuple of int : coordinates of rows
content -- str regex
style -- str
Return: list of rows
1318 def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row: 1319 """Get the row at the given "y" position. 1320 1321 Position start at 0. So cell A4 is on row 3. 1322 1323 A copy is returned, use set_cell() to push it back. 1324 1325 Arguments: 1326 1327 y -- int or str 1328 1329 Return: Row 1330 """ 1331 # fixme : keep repeat ? maybe an option to functions : "raw=False" 1332 y = self._translate_y_from_any(y) 1333 row = self._get_row2(y, clone=clone, create=create) 1334 if row is None: 1335 raise ValueError("Row not found") 1336 row.y = y 1337 return row
Get the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
A copy is returned, use set_cell() to push it back.
Arguments:
y -- int or str
Return: Row
1339 def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row: 1340 """Replace the row at the given position with the new one. Repetions of 1341 the old row will be adjusted. 1342 1343 If row is None, a new empty row is created. 1344 1345 Position start at 0. So cell A4 is on row 3. 1346 1347 Arguments: 1348 1349 y -- int or str 1350 1351 row -- Row 1352 1353 returns the row, with updated row.y 1354 """ 1355 if row is None: 1356 row = Row() 1357 repeated = 1 1358 clone = False 1359 else: 1360 repeated = row.repeated or 1 1361 y = self._translate_y_from_any(y) 1362 row.y = y 1363 # Outside the defined table ? 1364 diff = y - self.height 1365 if diff == 0: 1366 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1367 elif diff > 0: 1368 self.append_row(Row(repeated=diff), _repeated=diff, clone=clone) 1369 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1370 else: 1371 # Inside the defined table 1372 row_back = set_item_in_vault( # type: ignore 1373 y, row, self, _xpath_row_idx, "_tmap", clone=clone 1374 ) 1375 # print self.serialize(True) 1376 # Update width if necessary 1377 self._update_width(row_back) 1378 return row_back
Replace the row at the given position with the new one. Repetions of the old row will be adjusted.
If row is None, a new empty row is created.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
row -- Row
returns the row, with updated row.y
1380 def insert_row( 1381 self, y: str | int, row: Row | None = None, clone: bool = True 1382 ) -> Row: 1383 """Insert the row before the given "y" position. If no row is given, 1384 an empty one is created. 1385 1386 Position start at 0. So cell A4 is on row 3. 1387 1388 If row is None, a new empty row is created. 1389 1390 Arguments: 1391 1392 y -- int or str 1393 1394 row -- Row 1395 1396 returns the row, with updated row.y 1397 """ 1398 if row is None: 1399 row = Row() 1400 clone = False 1401 y = self._translate_y_from_any(y) 1402 diff = y - self.height 1403 if diff < 0: 1404 row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap") 1405 elif diff == 0: 1406 row_back = self.append_row(row, clone=clone) 1407 else: 1408 self.append_row(Row(repeated=diff), _repeated=diff, clone=False) 1409 row_back = self.append_row(row, clone=clone) 1410 row_back.y = y # type: ignore 1411 # Update width if necessary 1412 self._update_width(row_back) # type: ignore 1413 return row_back # type: ignore
Insert the row before the given "y" position. If no row is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
If row is None, a new empty row is created.
Arguments:
y -- int or str
row -- Row
returns the row, with updated row.y
1415 def extend_rows(self, rows: list[Row] | None = None) -> None: 1416 """Append a list of rows at the end of the table. 1417 1418 Arguments: 1419 1420 rows -- list of Row 1421 """ 1422 if rows is None: 1423 rows = [] 1424 self.extend(rows) 1425 self._compute_table_cache() 1426 # Update width if necessary 1427 width = self.width 1428 for row in self.traverse(): 1429 if row.width > width: 1430 width = row.width 1431 diff = width - self.width 1432 if diff > 0: 1433 self.append_column(Column(repeated=diff))
Append a list of rows at the end of the table.
Arguments:
rows -- list of Row
1435 def append_row( 1436 self, 1437 row: Row | None = None, 1438 clone: bool = True, 1439 _repeated: int | None = None, 1440 ) -> Row: 1441 """Append the row at the end of the table. If no row is given, an 1442 empty one is created. 1443 1444 Position start at 0. So cell A4 is on row 3. 1445 1446 Note the columns are automatically created when the first row is 1447 inserted in an empty table. So better insert a filled row. 1448 1449 Arguments: 1450 1451 row -- Row 1452 1453 _repeated -- (optional), repeated value of the row 1454 1455 returns the row, with updated row.y 1456 """ 1457 if row is None: 1458 row = Row() 1459 _repeated = 1 1460 elif clone: 1461 row = row.clone 1462 # Appending a repeated row accepted 1463 # Do not insert next to the last row because it could be in a group 1464 self._append(row) 1465 if _repeated is None: 1466 _repeated = row.repeated or 1 1467 self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated) 1468 row.y = self.height - 1 1469 # Initialize columns 1470 if not self._get_columns(): 1471 repeated = row.width 1472 self.insert(Column(repeated=repeated), position=0) 1473 self._compute_table_cache() 1474 # Update width if necessary 1475 self._update_width(row) 1476 return row
Append the row at the end of the table. If no row is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
Note the columns are automatically created when the first row is inserted in an empty table. So better insert a filled row.
Arguments:
row -- Row
_repeated -- (optional), repeated value of the row
returns the row, with updated row.y
1478 def delete_row(self, y: int | str) -> None: 1479 """Delete the row at the given "y" position. 1480 1481 Position start at 0. So cell A4 is on row 3. 1482 1483 Arguments: 1484 1485 y -- int or str 1486 """ 1487 y = self._translate_y_from_any(y) 1488 # Outside the defined table 1489 if y >= self.height: 1490 return 1491 # Inside the defined table 1492 delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")
Delete the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
1494 def get_row_values( 1495 self, 1496 y: int | str, 1497 cell_type: str | None = None, 1498 complete: bool = True, 1499 get_type: bool = False, 1500 ) -> list: 1501 """Shortcut to get the list of Python values for the cells of the row 1502 at the given "y" position. 1503 1504 Position start at 0. So cell A4 is on row 3. 1505 1506 Filter by cell_type, with cell_type 'all' will retrieve cells of any 1507 type, aka non empty cells. 1508 If cell_type and complete is True, replace missing values by None. 1509 1510 If get_type is True, returns a tuple (value, ODF type of value) 1511 1512 Arguments: 1513 1514 y -- int, str 1515 1516 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1517 'currency', 'percentage' or 'all' 1518 1519 complete -- boolean 1520 1521 get_type -- boolean 1522 1523 Return: list of lists of Python types 1524 """ 1525 values = self.get_row(y, clone=False).get_values( 1526 cell_type=cell_type, complete=complete, get_type=get_type 1527 ) 1528 # complete row to match column width 1529 if complete: 1530 if get_type: 1531 values.extend([(None, None)] * (self.width - len(values))) 1532 else: 1533 values.extend([None] * (self.width - len(values))) 1534 return values
Shortcut to get the list of Python values for the cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.
If get_type is True, returns a tuple (value, ODF type of value)
Arguments:
y -- int, str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of lists of Python types
1536 def set_row_values( 1537 self, 1538 y: int | str, 1539 values: list, 1540 cell_type: str | None = None, 1541 currency: str | None = None, 1542 style: str | None = None, 1543 ) -> Row: 1544 """Shortcut to set the values of *all* cells of the row at the given 1545 "y" position. 1546 1547 Position start at 0. So cell A4 is on row 3. 1548 1549 Arguments: 1550 1551 y -- int or str 1552 1553 values -- list of Python types 1554 1555 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1556 'string' or 'time' 1557 1558 currency -- three-letter str 1559 1560 style -- str 1561 1562 returns the row, with updated row.y 1563 """ 1564 row = Row() # needed if clones rows 1565 row.set_values(values, style=style, cell_type=cell_type, currency=currency) 1566 return self.set_row(y, row) # needed if clones rows
Shortcut to set the values of all cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
values -- list of Python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
returns the row, with updated row.y
1568 def set_row_cells(self, y: int | str, cells: list | None = None) -> Row: 1569 """Shortcut to set *all* the cells of the row at the given 1570 "y" position. 1571 1572 Position start at 0. So cell A4 is on row 3. 1573 1574 Arguments: 1575 1576 y -- int or str 1577 1578 cells -- list of Python types 1579 1580 style -- str 1581 1582 returns the row, with updated row.y 1583 """ 1584 if cells is None: 1585 cells = [] 1586 row = Row() # needed if clones rows 1587 row.extend_cells(cells) 1588 return self.set_row(y, row) # needed if clones rows
Shortcut to set all the cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
cells -- list of Python types
style -- str
returns the row, with updated row.y
1590 def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool: 1591 """Return wether every cell in the row at the given "y" position has 1592 no value or the value evaluates to False (empty string), and no style. 1593 1594 Position start at 0. So cell A4 is on row 3. 1595 1596 If aggressive is True, empty cells with style are considered empty. 1597 1598 Arguments: 1599 1600 y -- int or str 1601 1602 aggressive -- bool 1603 """ 1604 return self.get_row(y, clone=False).is_empty(aggressive=aggressive)
Return wether every cell in the row at the given "y" position has no value or the value evaluates to False (empty string), and no style.
Position start at 0. So cell A4 is on row 3.
If aggressive is True, empty cells with style are considered empty.
Arguments:
y -- int or str
aggressive -- bool
1610 def get_cells( 1611 self, 1612 coord: tuple | list | str | None = None, 1613 cell_type: str | None = None, 1614 style: str | None = None, 1615 content: str | None = None, 1616 flat: bool = False, 1617 ) -> list: 1618 """Get the cells matching the criteria. If 'coord' is None, 1619 parse the whole table, else parse the area defined by 'coord'. 1620 1621 Filter by cell_type = "all" will retrieve cells of any 1622 type, aka non empty cells. 1623 1624 If flat is True (default is False), the method return a single list 1625 of all the values, else a list of lists of cells. 1626 1627 if cell_type, style and content are None, get_cells() will return 1628 the exact number of cells of the area, including empty cells. 1629 1630 Arguments: 1631 1632 coordinates -- str or tuple of int : coordinates of area 1633 1634 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1635 'currency', 'percentage' or 'all' 1636 1637 content -- str regex 1638 1639 style -- str 1640 1641 flat -- boolean 1642 1643 Return: list of tuples 1644 """ 1645 if coord: 1646 x, y, z, t = self._translate_table_coordinates(coord) 1647 else: 1648 x = y = z = t = None 1649 if flat: 1650 cells: list[Cell] = [] 1651 for row in self.traverse(start=y, end=t): 1652 row_cells = row.get_cells( 1653 coord=(x, z), 1654 cell_type=cell_type, 1655 style=style, 1656 content=content, 1657 ) 1658 cells.extend(row_cells) 1659 return cells 1660 else: 1661 lcells: list[list[Cell]] = [] 1662 for row in self.traverse(start=y, end=t): 1663 row_cells = row.get_cells( 1664 coord=(x, z), 1665 cell_type=cell_type, 1666 style=style, 1667 content=content, 1668 ) 1669 lcells.append(row_cells) 1670 return lcells
Get the cells matching the criteria. If 'coord' is None, parse the whole table, else parse the area defined by 'coord'.
Filter by cell_type = "all" will retrieve cells of any type, aka non empty cells.
If flat is True (default is False), the method return a single list of all the values, else a list of lists of cells.
if cell_type, style and content are None, get_cells() will return the exact number of cells of the area, including empty cells.
Arguments:
coordinates -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
flat -- boolean
Return: list of tuples
1672 def get_cell( 1673 self, 1674 coord: tuple | list | str, 1675 clone: bool = True, 1676 keep_repeated: bool = True, 1677 ) -> Cell: 1678 """Get the cell at the given coordinates. 1679 1680 They are either a 2-uplet of (x, y) starting from 0, or a 1681 human-readable position like "C4". 1682 1683 A copy is returned, use ``set_cell`` to push it back. 1684 1685 Arguments: 1686 1687 coord -- (int, int) or str 1688 1689 Return: Cell 1690 """ 1691 x, y = self._translate_cell_coordinates(coord) 1692 if x is None: 1693 raise ValueError 1694 if y is None: 1695 raise ValueError 1696 # Outside the defined table 1697 if y >= self.height: 1698 cell = Cell() 1699 else: 1700 # Inside the defined table 1701 row = self._get_row2_base(y) 1702 if row is None: 1703 raise ValueError 1704 read_cell = row.get_cell(x, clone=clone) 1705 if read_cell is None: 1706 raise ValueError 1707 cell = read_cell 1708 if not keep_repeated: 1709 repeated = cell.repeated or 1 1710 if repeated >= 2: 1711 cell.repeated = None 1712 cell.x = x 1713 cell.y = y 1714 return cell
Get the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
A copy is returned, use set_cell to push it back.
Arguments:
coord -- (int, int) or str
Return: Cell
1716 def get_value( 1717 self, 1718 coord: tuple | list | str, 1719 get_type: bool = False, 1720 ) -> Any: 1721 """Shortcut to get the Python value of the cell at the given 1722 coordinates. 1723 1724 If get_type is True, returns the tuples (value, ODF type) 1725 1726 coord is either a 2-uplet of (x, y) starting from 0, or a 1727 human-readable position like "C4". If an Area is given, the upper 1728 left position is used as coord. 1729 1730 Arguments: 1731 1732 coord -- (int, int) or str : coordinate 1733 1734 Return: Python type 1735 """ 1736 x, y = self._translate_cell_coordinates(coord) 1737 if x is None: 1738 raise ValueError 1739 if y is None: 1740 raise ValueError 1741 # Outside the defined table 1742 if y >= self.height: 1743 if get_type: 1744 return (None, None) 1745 return None 1746 else: 1747 # Inside the defined table 1748 row = self._get_row2_base(y) 1749 if row is None: 1750 raise ValueError 1751 cell = row._get_cell2_base(x) 1752 if cell is None: 1753 if get_type: 1754 return (None, None) 1755 return None 1756 return cell.get_value(get_type=get_type)
Shortcut to get the Python value of the cell at the given coordinates.
If get_type is True, returns the tuples (value, ODF type)
coord is either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4". If an Area is given, the upper left position is used as coord.
Arguments:
coord -- (int, int) or str : coordinate
Return: Python type
1758 def set_cell( 1759 self, 1760 coord: tuple | list | str, 1761 cell: Cell | None = None, 1762 clone: bool = True, 1763 ) -> Cell: 1764 """Replace a cell of the table at the given coordinates. 1765 1766 They are either a 2-uplet of (x, y) starting from 0, or a 1767 human-readable position like "C4". 1768 1769 Arguments: 1770 1771 coord -- (int, int) or str : coordinate 1772 1773 cell -- Cell 1774 1775 return the cell, with x and y updated 1776 """ 1777 if cell is None: 1778 cell = Cell() 1779 clone = False 1780 x, y = self._translate_cell_coordinates(coord) 1781 if x is None: 1782 raise ValueError 1783 if y is None: 1784 raise ValueError 1785 cell.x = x 1786 cell.y = y 1787 if y >= self.height: 1788 row = Row() 1789 cell_back = row.set_cell(x, cell, clone=clone) 1790 self.set_row(y, row, clone=False) 1791 else: 1792 row_read = self._get_row2_base(y) 1793 if row_read is None: 1794 raise ValueError 1795 row = row_read 1796 row.y = y 1797 repeated = row.repeated or 1 1798 if repeated > 1: 1799 row = row.clone 1800 row.repeated = None 1801 cell_back = row.set_cell(x, cell, clone=clone) 1802 self.set_row(y, row, clone=False) 1803 else: 1804 cell_back = row.set_cell(x, cell, clone=clone) 1805 # Update width if necessary, since we don't use set_row 1806 self._update_width(row) 1807 return cell_back
Replace a cell of the table at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Arguments:
coord -- (int, int) or str : coordinate
cell -- Cell
return the cell, with x and y updated
1809 def set_cells( 1810 self, 1811 cells: list[list[Cell]] | list[tuple[Cell]], 1812 coord: tuple | list | str | None = None, 1813 clone: bool = True, 1814 ) -> None: 1815 """Set the cells in the table, from the 'coord' position. 1816 1817 'coord' is the coordinate of the upper left cell to be modified by 1818 values. If 'coord' is None, default to the position (0,0) ("A1"). 1819 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1820 area is used as coordinate. 1821 1822 The table is *not* cleared before the operation, to reset the table 1823 before setting cells, use table.clear(). 1824 1825 A list of lists is expected, with as many lists as rows to be set, and 1826 as many cell in each sublist as cells to be setted in the row. 1827 1828 Arguments: 1829 1830 cells -- list of list of cells 1831 1832 coord -- tuple or str 1833 1834 values -- list of lists of python types 1835 """ 1836 if coord: 1837 x, y = self._translate_cell_coordinates(coord) 1838 else: 1839 x = y = 0 1840 if y is None: 1841 y = 0 1842 if x is None: 1843 x = 0 1844 y -= 1 1845 for row_cells in cells: 1846 y += 1 1847 if not row_cells: 1848 continue 1849 row = self.get_row(y, clone=True) 1850 repeated = row.repeated or 1 1851 if repeated >= 2: 1852 row.repeated = None 1853 row.set_cells(row_cells, start=x, clone=clone) 1854 self.set_row(y, row, clone=False) 1855 self._update_width(row)
Set the cells in the table, from the 'coord' position.
'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.
The table is not cleared before the operation, to reset the table before setting cells, use table.clear().
A list of lists is expected, with as many lists as rows to be set, and as many cell in each sublist as cells to be setted in the row.
Arguments:
cells -- list of list of cells
coord -- tuple or str
values -- list of lists of python types
1857 def set_value( 1858 self, 1859 coord: tuple | list | str, 1860 value: Any, 1861 cell_type: str | None = None, 1862 currency: str | None = None, 1863 style: str | None = None, 1864 ) -> None: 1865 """Set the Python value of the cell at the given coordinates. 1866 1867 They are either a 2-uplet of (x, y) starting from 0, or a 1868 human-readable position like "C4". 1869 1870 Arguments: 1871 1872 coord -- (int, int) or str 1873 1874 value -- Python type 1875 1876 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1877 'string' or 'time' 1878 1879 currency -- three-letter str 1880 1881 style -- str 1882 1883 """ 1884 self.set_cell( 1885 coord, 1886 Cell(value, cell_type=cell_type, currency=currency, style=style), 1887 clone=False, 1888 )
Set the Python value of the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Arguments:
coord -- (int, int) or str
value -- Python type
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
1890 def set_cell_image( 1891 self, 1892 coord: tuple | list | str, 1893 image_frame: Frame, 1894 doc_type: str | None = None, 1895 ) -> None: 1896 """Do all the magic to display an image in the cell at the given 1897 coordinates. 1898 1899 They are either a 2-uplet of (x, y) starting from 0, or a 1900 human-readable position like "C4". 1901 1902 The frame element must contain the expected image position and 1903 dimensions. 1904 1905 DrawImage insertion depends on the document type, so the type must be 1906 provided or the table element must be already attached to a document. 1907 1908 Arguments: 1909 1910 coord -- (int, int) or str 1911 1912 image_frame -- Frame including an image 1913 1914 doc_type -- 'spreadsheet' or 'text' 1915 """ 1916 # Test document type 1917 if doc_type is None: 1918 body = self.document_body 1919 if body is None: 1920 raise ValueError("document type not found") 1921 doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get( 1922 body.tag 1923 ) 1924 if doc_type is None: 1925 raise ValueError("document type not supported for images") 1926 # We need the end address of the image 1927 x, y = self._translate_cell_coordinates(coord) 1928 if x is None: 1929 raise ValueError 1930 if y is None: 1931 raise ValueError 1932 cell = self.get_cell((x, y)) 1933 image_frame = image_frame.clone # type: ignore 1934 # Remove any previous paragraph, frame, etc. 1935 for child in cell.children: 1936 cell.delete(child) 1937 # Now it all depends on the document type 1938 if doc_type == "spreadsheet": 1939 image_frame.anchor_type = "char" 1940 # The frame needs end coordinates 1941 width, height = image_frame.size 1942 image_frame.set_attribute("table:end-x", width) 1943 image_frame.set_attribute("table:end-y", height) 1944 # FIXME what happens when the address changes? 1945 address = f"{self.name}.{digit_to_alpha(x)}{y + 1}" 1946 image_frame.set_attribute("table:end-cell-address", address) 1947 # The frame is directly in the cell 1948 cell.append(image_frame) 1949 elif doc_type == "text": 1950 # The frame must be in a paragraph 1951 cell.set_value("") 1952 paragraph = cell.get_element("text:p") 1953 if paragraph is None: 1954 raise ValueError 1955 paragraph.append(image_frame) 1956 self.set_cell(coord, cell)
Do all the magic to display an image in the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
The frame element must contain the expected image position and dimensions.
DrawImage insertion depends on the document type, so the type must be provided or the table element must be already attached to a document.
Arguments:
coord -- (int, int) or str
image_frame -- Frame including an image
doc_type -- 'spreadsheet' or 'text'
1958 def insert_cell( 1959 self, 1960 coord: tuple | list | str, 1961 cell: Cell | None = None, 1962 clone: bool = True, 1963 ) -> Cell: 1964 """Insert the given cell at the given coordinates. If no cell is 1965 given, an empty one is created. 1966 1967 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 1968 human-readable position like "C4". 1969 1970 Cells on the right are shifted. Other rows remain untouched. 1971 1972 Arguments: 1973 1974 coord -- (int, int) or str 1975 1976 cell -- Cell 1977 1978 returns the cell with x and y updated 1979 """ 1980 if cell is None: 1981 cell = Cell() 1982 clone = False 1983 if clone: 1984 cell = cell.clone 1985 x, y = self._translate_cell_coordinates(coord) 1986 if x is None: 1987 raise ValueError 1988 if y is None: 1989 raise ValueError 1990 row = self._get_row2(y, clone=True) 1991 row.y = y 1992 row.repeated = None 1993 cell_back = row.insert_cell(x, cell, clone=False) 1994 self.set_row(y, row, clone=False) 1995 # Update width if necessary 1996 self._update_width(row) 1997 return cell_back
Insert the given cell at the given coordinates. If no cell is given, an empty one is created.
Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Cells on the right are shifted. Other rows remain untouched.
Arguments:
coord -- (int, int) or str
cell -- Cell
returns the cell with x and y updated
1999 def append_cell( 2000 self, 2001 y: int | str, 2002 cell: Cell | None = None, 2003 clone: bool = True, 2004 ) -> Cell: 2005 """Append the given cell at the "y" coordinate. Repeated cells are 2006 accepted. If no cell is given, an empty one is created. 2007 2008 Position start at 0. So cell A4 is on row 3. 2009 2010 Other rows remain untouched. 2011 2012 Arguments: 2013 2014 y -- int or str 2015 2016 cell -- Cell 2017 2018 returns the cell with x and y updated 2019 """ 2020 if cell is None: 2021 cell = Cell() 2022 clone = False 2023 if clone: 2024 cell = cell.clone 2025 y = self._translate_y_from_any(y) 2026 row = self._get_row2(y) 2027 row.y = y 2028 cell_back = row.append_cell(cell, clone=False) 2029 self.set_row(y, row) 2030 # Update width if necessary 2031 self._update_width(row) 2032 return cell_back
Append the given cell at the "y" coordinate. Repeated cells are accepted. If no cell is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
Other rows remain untouched.
Arguments:
y -- int or str
cell -- Cell
returns the cell with x and y updated
2034 def delete_cell(self, coord: tuple | list | str) -> None: 2035 """Delete the cell at the given coordinates, so that next cells are 2036 shifted to the left. 2037 2038 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2039 human-readable position like "C4". 2040 2041 Use set_value() for erasing value. 2042 2043 Arguments: 2044 2045 coord -- (int, int) or str 2046 """ 2047 x, y = self._translate_cell_coordinates(coord) 2048 if x is None: 2049 raise ValueError 2050 if y is None: 2051 raise ValueError 2052 # Outside the defined table 2053 if y >= self.height: 2054 return 2055 # Inside the defined table 2056 row = self._get_row2_base(y) 2057 if row is None: 2058 raise ValueError 2059 row.delete_cell(x) 2060 # self.set_row(y, row)
Delete the cell at the given coordinates, so that next cells are shifted to the left.
Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Use set_value() for erasing value.
Arguments:
coord -- (int, int) or str
2067 def traverse_columns( # noqa: C901 2068 self, 2069 start: int | None = None, 2070 end: int | None = None, 2071 ) -> Iterator[Column]: 2072 """Yield as many column elements as expected columns in the table, 2073 i.e. expand repetitions by returning the same column as many times as 2074 necessary. 2075 2076 Arguments: 2077 2078 start -- int 2079 2080 end -- int 2081 2082 Copies are returned, use set_column() to push them back. 2083 """ 2084 idx = -1 2085 before = -1 2086 x = 0 2087 if start is None and end is None: 2088 for juska in self._cmap: 2089 idx += 1 2090 if idx in self._indexes["_cmap"]: 2091 column = self._indexes["_cmap"][idx] 2092 else: 2093 column = self._get_element_idx2(_xpath_column_idx, idx) 2094 self._indexes["_cmap"][idx] = column 2095 repeated = juska - before 2096 before = juska 2097 for _i in range(repeated or 1): 2098 # Return a copy without the now obsolete repetition 2099 column = column.clone 2100 column.x = x 2101 x += 1 2102 if repeated > 1: 2103 column.repeated = None 2104 yield column 2105 else: 2106 if start is None: 2107 start = 0 2108 start = max(0, start) 2109 if end is None: 2110 try: 2111 end = self._cmap[-1] 2112 except Exception: 2113 end = -1 2114 start_map = find_odf_idx(self._cmap, start) 2115 if start_map is None: 2116 return 2117 if start_map > 0: 2118 before = self._cmap[start_map - 1] 2119 idx = start_map - 1 2120 before = start - 1 2121 x = start 2122 for juska in self._cmap[start_map:]: 2123 idx += 1 2124 if idx in self._indexes["_cmap"]: 2125 column = self._indexes["_cmap"][idx] 2126 else: 2127 column = self._get_element_idx2(_xpath_column_idx, idx) 2128 self._indexes["_cmap"][idx] = column 2129 repeated = juska - before 2130 before = juska 2131 for _i in range(repeated or 1): 2132 if x <= end: 2133 column = column.clone 2134 column.x = x 2135 x += 1 2136 if repeated > 1 or (x == start and start > 0): 2137 column.repeated = None 2138 yield column
Yield as many column elements as expected columns in the table, i.e. expand repetitions by returning the same column as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_column() to push them back.
2140 def get_columns( 2141 self, 2142 coord: tuple | list | str | None = None, 2143 style: str | None = None, 2144 ) -> list[Column]: 2145 """Get the list of columns matching the criteria. Each result is a 2146 tuple of (x, column). 2147 2148 Arguments: 2149 2150 coord -- str or tuple of int : coordinates of columns 2151 2152 style -- str 2153 2154 Return: list of columns 2155 """ 2156 if coord: 2157 x, _y, _z, t = self._translate_column_coordinates(coord) 2158 else: 2159 x = t = None 2160 if not style: 2161 return list(self.traverse_columns(start=x, end=t)) 2162 columns = [] 2163 for column in self.traverse_columns(start=x, end=t): 2164 if style != column.style: 2165 continue 2166 columns.append(column) 2167 return columns
Get the list of columns matching the criteria. Each result is a tuple of (x, column).
Arguments:
coord -- str or tuple of int : coordinates of columns
style -- str
Return: list of columns
2184 def get_column(self, x: int | str) -> Column: 2185 """Get the column at the given "x" position. 2186 2187 ODF columns don't contain cells, only style information. 2188 2189 Position start at 0. So cell C4 is on column 2. Alphabetical position 2190 like "C" is accepted. 2191 2192 A copy is returned, use set_column() to push it back. 2193 2194 Arguments: 2195 2196 x -- int or str 2197 2198 Return: Column 2199 """ 2200 x = self._translate_x_from_any(x) 2201 column = self._get_column2(x) 2202 if column is None: 2203 raise ValueError 2204 column.x = x 2205 return column
Get the column at the given "x" position.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
A copy is returned, use set_column() to push it back.
Arguments:
x -- int or str
Return: Column
2207 def set_column( 2208 self, 2209 x: int | str, 2210 column: Column | None = None, 2211 ) -> Column: 2212 """Replace the column at the given "x" position. 2213 2214 ODF columns don't contain cells, only style information. 2215 2216 Position start at 0. So cell C4 is on column 2. Alphabetical position 2217 like "C" is accepted. 2218 2219 Arguments: 2220 2221 x -- int or str 2222 2223 column -- Column 2224 """ 2225 x = self._translate_x_from_any(x) 2226 if column is None: 2227 column = Column() 2228 repeated = 1 2229 else: 2230 repeated = column.repeated or 1 2231 column.x = x 2232 # Outside the defined table ? 2233 diff = x - self.width 2234 if diff == 0: 2235 column_back = self.append_column(column, _repeated=repeated) 2236 elif diff > 0: 2237 self.append_column(Column(repeated=diff), _repeated=diff) 2238 column_back = self.append_column(column, _repeated=repeated) 2239 else: 2240 # Inside the defined table 2241 column_back = set_item_in_vault( # type: ignore 2242 x, column, self, _xpath_column_idx, "_cmap" 2243 ) 2244 return column_back
Replace the column at the given "x" position.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
column -- Column
2246 def insert_column( 2247 self, 2248 x: int | str, 2249 column: Column | None = None, 2250 ) -> Column: 2251 """Insert the column before the given "x" position. If no column is 2252 given, an empty one is created. 2253 2254 ODF columns don't contain cells, only style information. 2255 2256 Position start at 0. So cell C4 is on column 2. Alphabetical position 2257 like "C" is accepted. 2258 2259 Arguments: 2260 2261 x -- int or str 2262 2263 column -- Column 2264 """ 2265 if column is None: 2266 column = Column() 2267 x = self._translate_x_from_any(x) 2268 diff = x - self.width 2269 if diff < 0: 2270 column_back = insert_item_in_vault( 2271 x, column, self, _xpath_column_idx, "_cmap" 2272 ) 2273 elif diff == 0: 2274 column_back = self.append_column(column.clone) 2275 else: 2276 self.append_column(Column(repeated=diff), _repeated=diff) 2277 column_back = self.append_column(column.clone) 2278 column_back.x = x # type: ignore 2279 # Repetitions are accepted 2280 repeated = column.repeated or 1 2281 # Update width on every row 2282 for row in self._get_rows(): 2283 if row.width > x: 2284 row.insert_cell(x, Cell(repeated=repeated)) 2285 # Shorter rows don't need insert 2286 # Longer rows shouldn't exist! 2287 return column_back # type: ignore
Insert the column before the given "x" position. If no column is given, an empty one is created.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
column -- Column
2289 def append_column( 2290 self, 2291 column: Column | None = None, 2292 _repeated: int | None = None, 2293 ) -> Column: 2294 """Append the column at the end of the table. If no column is given, 2295 an empty one is created. 2296 2297 ODF columns don't contain cells, only style information. 2298 2299 Position start at 0. So cell C4 is on column 2. Alphabetical position 2300 like "C" is accepted. 2301 2302 Arguments: 2303 2304 column -- Column 2305 """ 2306 if column is None: 2307 column = Column() 2308 else: 2309 column = column.clone 2310 if not self._cmap: 2311 position = 0 2312 else: 2313 odf_idx = len(self._cmap) - 1 2314 last_column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2315 if last_column is None: 2316 raise ValueError 2317 position = self.index(last_column) + 1 2318 column.x = self.width 2319 self.insert(column, position=position) 2320 # Repetitions are accepted 2321 if _repeated is None: 2322 _repeated = column.repeated or 1 2323 self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated) 2324 # No need to update row widths 2325 return column
Append the column at the end of the table. If no column is given, an empty one is created.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
column -- Column
2327 def delete_column(self, x: int | str) -> None: 2328 """Delete the column at the given position. ODF columns don't contain 2329 cells, only style information. 2330 2331 Position start at 0. So cell C4 is on column 2. Alphabetical position 2332 like "C" is accepted. 2333 2334 Arguments: 2335 2336 x -- int or str 2337 """ 2338 x = self._translate_x_from_any(x) 2339 # Outside the defined table 2340 if x >= self.width: 2341 return 2342 # Inside the defined table 2343 delete_item_in_vault(x, self, _xpath_column_idx, "_cmap") 2344 # Update width 2345 width = self.width 2346 for row in self._get_rows(): 2347 if row.width >= width: 2348 row.delete_cell(x)
Delete the column at the given position. ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
2350 def get_column_cells( # noqa: C901 2351 self, 2352 x: int | str, 2353 style: str | None = None, 2354 content: str | None = None, 2355 cell_type: str | None = None, 2356 complete: bool = False, 2357 ) -> list[Cell | None]: 2358 """Get the list of cells at the given position. 2359 2360 Position start at 0. So cell C4 is on column 2. Alphabetical position 2361 like "C" is accepted. 2362 2363 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2364 type, aka non empty cells. 2365 2366 If complete is True, replace missing values by None. 2367 2368 Arguments: 2369 2370 x -- int or str 2371 2372 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2373 'currency', 'percentage' or 'all' 2374 2375 content -- str regex 2376 2377 style -- str 2378 2379 complete -- boolean 2380 2381 Return: list of Cell 2382 """ 2383 x = self._translate_x_from_any(x) 2384 if cell_type: 2385 cell_type = cell_type.lower().strip() 2386 cells: list[Cell | None] = [] 2387 if not style and not content and not cell_type: 2388 for row in self.traverse(): 2389 cells.append(row.get_cell(x, clone=True)) 2390 return cells 2391 for row in self.traverse(): 2392 cell = row.get_cell(x, clone=True) 2393 if cell is None: 2394 raise ValueError 2395 # Filter the cells by cell_type 2396 if cell_type: 2397 ctype = cell.type 2398 if not ctype or not (ctype == cell_type or cell_type == "all"): 2399 if complete: 2400 cells.append(None) 2401 continue 2402 # Filter the cells with the regex 2403 if content and not cell.match(content): 2404 if complete: 2405 cells.append(None) 2406 continue 2407 # Filter the cells with the style 2408 if style and style != cell.style: 2409 if complete: 2410 cells.append(None) 2411 continue 2412 cells.append(cell) 2413 return cells
Get the list of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.
If complete is True, replace missing values by None.
Arguments:
x -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
complete -- boolean
Return: list of Cell
2415 def get_column_values( 2416 self, 2417 x: int | str, 2418 cell_type: str | None = None, 2419 complete: bool = True, 2420 get_type: bool = False, 2421 ) -> list[Any]: 2422 """Shortcut to get the list of Python values for the cells at the 2423 given position. 2424 2425 Position start at 0. So cell C4 is on column 2. Alphabetical position 2426 like "C" is accepted. 2427 2428 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2429 type, aka non empty cells. 2430 If cell_type and complete is True, replace missing values by None. 2431 2432 If get_type is True, returns a tuple (value, ODF type of value) 2433 2434 Arguments: 2435 2436 x -- int or str 2437 2438 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2439 'currency', 'percentage' or 'all' 2440 2441 complete -- boolean 2442 2443 get_type -- boolean 2444 2445 Return: list of Python types 2446 """ 2447 cells = self.get_column_cells( 2448 x, style=None, content=None, cell_type=cell_type, complete=complete 2449 ) 2450 values: list[Any] = [] 2451 for cell in cells: 2452 if cell is None: 2453 if complete: 2454 if get_type: 2455 values.append((None, None)) 2456 else: 2457 values.append(None) 2458 continue 2459 if cell_type: 2460 ctype = cell.type 2461 if not ctype or not (ctype == cell_type or cell_type == "all"): 2462 if complete: 2463 if get_type: 2464 values.append((None, None)) 2465 else: 2466 values.append(None) 2467 continue 2468 values.append(cell.get_value(get_type=get_type)) 2469 return values
Shortcut to get the list of Python values for the cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.
If get_type is True, returns a tuple (value, ODF type of value)
Arguments:
x -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of Python types
2471 def set_column_cells(self, x: int | str, cells: list[Cell]) -> None: 2472 """Shortcut to set the list of cells at the given position. 2473 2474 Position start at 0. So cell C4 is on column 2. Alphabetical position 2475 like "C" is accepted. 2476 2477 The list must have the same length than the table height. 2478 2479 Arguments: 2480 2481 x -- int or str 2482 2483 cells -- list of Cell 2484 """ 2485 height = self.height 2486 if len(cells) != height: 2487 raise ValueError(f"col mismatch: {height} cells expected") 2488 cells_iterator = iter(cells) 2489 for y, row in enumerate(self.traverse()): 2490 row.set_cell(x, next(cells_iterator)) 2491 self.set_row(y, row)
Shortcut to set the list of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
The list must have the same length than the table height.
Arguments:
x -- int or str
cells -- list of Cell
2493 def set_column_values( 2494 self, 2495 x: int | str, 2496 values: list, 2497 cell_type: str | None = None, 2498 currency: str | None = None, 2499 style: str | None = None, 2500 ) -> None: 2501 """Shortcut to set the list of Python values of cells at the given 2502 position. 2503 2504 Position start at 0. So cell C4 is on column 2. Alphabetical position 2505 like "C" is accepted. 2506 2507 The list must have the same length than the table height. 2508 2509 Arguments: 2510 2511 x -- int or str 2512 2513 values -- list of Python types 2514 2515 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 2516 'string' or 'time' 2517 2518 currency -- three-letter str 2519 2520 style -- str 2521 """ 2522 cells = [ 2523 Cell(value, cell_type=cell_type, currency=currency, style=style) 2524 for value in values 2525 ] 2526 self.set_column_cells(x, cells)
Shortcut to set the list of Python values of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
The list must have the same length than the table height.
Arguments:
x -- int or str
values -- list of Python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
2528 def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool: 2529 """Return wether every cell in the column at "x" position has no value 2530 or the value evaluates to False (empty string), and no style. 2531 2532 Position start at 0. So cell C4 is on column 2. Alphabetical position 2533 like "C" is accepted. 2534 2535 If aggressive is True, empty cells with style are considered empty. 2536 2537 Return: bool 2538 """ 2539 for cell in self.get_column_cells(x): 2540 if cell is None: 2541 continue 2542 if not cell.is_empty(aggressive=aggressive): 2543 return False 2544 return True
Return wether every cell in the column at "x" position has no value or the value evaluates to False (empty string), and no style.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
If aggressive is True, empty cells with style are considered empty.
Return: bool
2548 def get_named_ranges( # type: ignore 2549 self, 2550 table_name: str | list[str] | None = None, 2551 ) -> list[NamedRange]: 2552 """Returns the list of available Name Ranges of the spreadsheet. If 2553 table_name is provided, limits the search to these tables. 2554 Beware : named ranges are stored at the body level, thus do not call 2555 this method on a cloned table. 2556 2557 Arguments: 2558 2559 table_names -- str or list of str, names of tables 2560 2561 Return : list of table_range 2562 """ 2563 body = self.document_body 2564 if not body: 2565 return [] 2566 all_named_ranges = body.get_named_ranges() 2567 if not table_name: 2568 return all_named_ranges # type:ignore 2569 filter_ = [] 2570 if isinstance(table_name, str): 2571 filter_.append(table_name) 2572 elif isiterable(table_name): 2573 filter_.extend(table_name) 2574 else: 2575 raise ValueError( 2576 f"table_name must be string or Iterable, not {type(table_name)}" 2577 ) 2578 return [ 2579 nr for nr in all_named_ranges if nr.table_name in filter_ # type:ignore 2580 ]
Returns the list of available Name Ranges of the spreadsheet. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
table_names -- str or list of str, names of tables
Return : list of table_range
2582 def get_named_range(self, name: str) -> NamedRange: 2583 """Returns the Name Ranges of the specified name. If 2584 table_name is provided, limits the search to these tables. 2585 Beware : named ranges are stored at the body level, thus do not call 2586 this method on a cloned table. 2587 2588 Arguments: 2589 2590 name -- str, name of the named range object 2591 2592 Return : NamedRange 2593 """ 2594 body = self.document_body 2595 if not body: 2596 raise ValueError("Table is not inside a document") 2597 return body.get_named_range(name) # type: ignore
Returns the Name Ranges of the specified name. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str, name of the named range object
Return : NamedRange
2599 def set_named_range( 2600 self, 2601 name: str, 2602 crange: str | tuple | list, 2603 table_name: str | None = None, 2604 usage: str | None = None, 2605 ) -> None: 2606 """Create a Named Range element and insert it in the document. 2607 Beware : named ranges are stored at the body level, thus do not call 2608 this method on a cloned table. 2609 2610 Arguments: 2611 2612 name -- str, name of the named range 2613 2614 crange -- str or tuple of int, cell or area coordinate 2615 2616 table_name -- str, name of the table 2617 2618 uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2619 """ 2620 body = self.document_body 2621 if not body: 2622 raise ValueError("Table is not inside a document") 2623 if not name: 2624 raise ValueError("Name required.") 2625 if table_name is None: 2626 table_name = self.name 2627 named_range = NamedRange(name, crange, table_name, usage) 2628 body.append_named_range(named_range)
Create a Named Range element and insert it in the document. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str, name of the named range
crange -- str or tuple of int, cell or area coordinate
table_name -- str, name of the table
uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2630 def delete_named_range(self, name: str) -> None: 2631 """Delete the Named Range of specified name from the spreadsheet. 2632 Beware : named ranges are stored at the body level, thus do not call 2633 this method on a cloned table. 2634 2635 Arguments: 2636 2637 name -- str 2638 """ 2639 name = name.strip() 2640 if not name: 2641 raise ValueError("Name required.") 2642 body = self.document_body 2643 if not body: 2644 raise ValueError("Table is not inside a document.") 2645 body.delete_named_range(name)
Delete the Named Range of specified name from the spreadsheet. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str
2651 def set_span( # noqa: C901 2652 self, 2653 area: str | tuple | list, 2654 merge: bool = False, 2655 ) -> bool: 2656 """Create a Cell Span : span the first cell of the area on several 2657 columns and/or rows. 2658 If merge is True, replace text of the cell by the concatenation of 2659 existing text in covered cells. 2660 Beware : if merge is True, old text is changed, if merge is False 2661 (the default), old text in coverd cells is still present but not 2662 displayed by most GUI. 2663 2664 If the area defines only one cell, the set span will do nothing. 2665 It is not allowed to apply set span to an area whose one cell already 2666 belongs to previous cell span. 2667 2668 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2669 be provided as an alpha numeric value like "A1:B2' or a tuple like 2670 (0, 0, 1, 1) or (0, 0). 2671 2672 Arguments: 2673 2674 area -- str or tuple of int, cell or area coordinate 2675 2676 merge -- boolean 2677 """ 2678 # get area 2679 digits = convert_coordinates(area) 2680 if len(digits) == 4: 2681 x, y, z, t = digits 2682 else: 2683 x, y = digits 2684 z, t = digits 2685 start = x, y 2686 end = z, t 2687 if start == end: 2688 # one cell : do nothing 2689 return False 2690 if x is None: 2691 raise ValueError 2692 if y is None: 2693 raise ValueError 2694 if z is None: 2695 raise ValueError 2696 if t is None: 2697 raise ValueError 2698 # check for previous span 2699 good = True 2700 # Check boundaries and empty cells : need to crate non existent cells 2701 # so don't use get_cells directly, but get_cell 2702 cells = [] 2703 for yy in range(y, t + 1): 2704 row_cells = [] 2705 for xx in range(x, z + 1): 2706 row_cells.append( 2707 self.get_cell((xx, yy), clone=True, keep_repeated=False) 2708 ) 2709 cells.append(row_cells) 2710 for row in cells: 2711 for cell in row: 2712 if cell._is_spanned(): 2713 good = False 2714 break 2715 if not good: 2716 break 2717 if not good: 2718 return False 2719 # Check boundaries 2720 # if z >= self.width or t >= self.height: 2721 # self.set_cell(coord = end) 2722 # print area, z, t 2723 # cells = self.get_cells((x, y, z, t)) 2724 # print cells 2725 # do it: 2726 if merge: 2727 val_list = [] 2728 for row in cells: 2729 for cell in row: 2730 if cell.is_empty(aggressive=True): 2731 continue 2732 val = cell.get_value() 2733 if val is not None: 2734 if isinstance(val, str): 2735 val.strip() 2736 if val != "": 2737 val_list.append(val) 2738 cell.clear() 2739 if val_list: 2740 if len(val_list) == 1: 2741 cells[0][0].set_value(val_list[0]) 2742 else: 2743 value = " ".join([str(v) for v in val_list if v]) 2744 cells[0][0].set_value(value) 2745 cols = z - x + 1 2746 cells[0][0].set_attribute("table:number-columns-spanned", str(cols)) 2747 rows = t - y + 1 2748 cells[0][0].set_attribute("table:number-rows-spanned", str(rows)) 2749 for cell in cells[0][1:]: 2750 cell.tag = "table:covered-table-cell" 2751 for row in cells[1:]: 2752 for cell in row: 2753 cell.tag = "table:covered-table-cell" 2754 # replace cells in table 2755 self.set_cells(cells, coord=start, clone=False) 2756 return True
Create a Cell Span : span the first cell of the area on several columns and/or rows. If merge is True, replace text of the cell by the concatenation of existing text in covered cells. Beware : if merge is True, old text is changed, if merge is False (the default), old text in coverd cells is still present but not displayed by most GUI.
If the area defines only one cell, the set span will do nothing. It is not allowed to apply set span to an area whose one cell already belongs to previous cell span.
Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
Arguments:
area -- str or tuple of int, cell or area coordinate
merge -- boolean
2758 def del_span(self, area: str | tuple | list) -> bool: 2759 """Delete a Cell Span. 'area' is the cell coordiante of the upper left 2760 cell of the spanned area. 2761 2762 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2763 be provided as an alpha numeric value like "A1:B2' or a tuple like 2764 (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell 2765 is used. 2766 2767 Arguments: 2768 2769 area -- str or tuple of int, cell or area coordinate 2770 """ 2771 # get area 2772 digits = convert_coordinates(area) 2773 if len(digits) == 4: 2774 x, y, _z, _t = digits 2775 else: 2776 x, y = digits 2777 if x is None: 2778 raise ValueError 2779 if y is None: 2780 raise ValueError 2781 start = x, y 2782 # check for previous span 2783 cell0 = self.get_cell(start) 2784 nb_cols = cell0.get_attribute_integer("table:number-columns-spanned") 2785 if nb_cols is None: 2786 return False 2787 nb_rows = cell0.get_attribute_integer("table:number-rows-spanned") 2788 if nb_rows is None: 2789 return False 2790 z = x + nb_cols - 1 2791 t = y + nb_rows - 1 2792 cells = self.get_cells((x, y, z, t)) 2793 cells[0][0].del_attribute("table:number-columns-spanned") 2794 cells[0][0].del_attribute("table:number-rows-spanned") 2795 for cell in cells[0][1:]: 2796 cell.tag = "table:table-cell" 2797 for row in cells[1:]: 2798 for cell in row: 2799 cell.tag = "table:table-cell" 2800 # replace cells in table 2801 self.set_cells(cells, coord=start, clone=False) 2802 return True
Delete a Cell Span. 'area' is the cell coordiante of the upper left cell of the spanned area.
Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell is used.
Arguments:
area -- str or tuple of int, cell or area coordinate
2806 def to_csv( 2807 self, 2808 path_or_file: str | Path | None = None, 2809 dialect: str = "excel", 2810 ) -> Any: 2811 """Write the table as CSV in the file. 2812 2813 If the file is a string, it is opened as a local path. Else an 2814 opened file-like is expected. 2815 2816 Arguments: 2817 2818 path_or_file -- str or file-like 2819 2820 dialect -- str, python csv.dialect, can be 'excel', 'unix'... 2821 """ 2822 2823 def write_content(csv_writer: object) -> None: 2824 for values in self.iter_values(): 2825 line = [] 2826 for value in values: 2827 if value is None: 2828 value = "" 2829 if isinstance(value, str): 2830 value = value.strip() 2831 line.append(value) 2832 csv_writer.writerow(line) # type: ignore 2833 2834 out = StringIO(newline="") 2835 csv_writer = csv.writer(out, dialect=dialect) 2836 write_content(csv_writer) 2837 if path_or_file is None: 2838 return out.getvalue() 2839 path = Path(path_or_file) 2840 path.write_text(out.getvalue()) 2841 return None
Write the table as CSV in the file.
If the file is a string, it is opened as a local path. Else an opened file-like is expected.
Arguments:
path_or_file -- str or file-like
dialect -- str, python csv.dialect, can be 'excel', 'unix'...
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- append_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
273class Text(str): 274 """Representation of an XML text node. Created to hide the specifics of 275 lxml in searching text nodes using XPath. 276 277 Constructed like any str object but only accepts lxml text objects. 278 """ 279 280 # There's some black magic in inheriting from str 281 def __init__( 282 self, 283 text_result: _ElementUnicodeResult | _ElementStringResult, 284 ) -> None: 285 self.__parent = text_result.getparent() 286 self.__is_text = text_result.is_text 287 self.__is_tail = text_result.is_tail 288 289 @property 290 def parent(self) -> Element | None: 291 parent = self.__parent 292 # XXX happens just because of the unit test 293 if parent is None: 294 return None 295 return Element.from_tag(tag_or_elem=parent) 296 297 def is_text(self) -> bool: 298 return self.__is_text 299 300 def is_tail(self) -> bool: 301 return self.__is_tail
Representation of an XML text node. Created to hide the specifics of lxml in searching text nodes using XPath.
Constructed like any str object but only accepts lxml text objects.
Inherited Members
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
508class TextChange(Element): 509 """The TextChange "text:change" element marks a position in an empty 510 region where text has been deleted. 511 """ 512 513 _tag = "text:change" 514 515 def get_id(self) -> str | None: 516 return self.get_attribute_string("text:change-id") 517 518 def set_id(self, idx: str) -> None: 519 self.set_attribute("text:change-id", idx) 520 521 def _get_tracked_changes(self) -> Element | None: 522 body = self.document_body 523 if not body: 524 raise ValueError 525 return body.get_tracked_changes() 526 527 def get_changed_region( 528 self, 529 tracked_changes: Element | None = None, 530 ) -> Element | None: 531 if not tracked_changes: 532 tracked_changes = self._get_tracked_changes() 533 idx = self.get_id() 534 return tracked_changes.get_changed_region(text_id=idx) # type: ignore 535 536 def get_change_info( 537 self, 538 tracked_changes: Element | None = None, 539 ) -> Element | None: 540 changed_region = self.get_changed_region(tracked_changes=tracked_changes) 541 if not changed_region: 542 return None 543 return changed_region.get_change_info() # type: ignore 544 545 def get_change_element( 546 self, 547 tracked_changes: Element | None = None, 548 ) -> Element | None: 549 changed_region = self.get_changed_region(tracked_changes=tracked_changes) 550 if not changed_region: 551 return None 552 return changed_region.get_change_element() # type: ignore 553 554 def get_deleted( 555 self, 556 tracked_changes: Element | None = None, 557 as_text: bool = False, 558 no_header: bool = False, 559 clean: bool = True, 560 ) -> Element | None: 561 """Shortcut to get the deleted informations stored in the 562 TextDeletion stored in the tracked changes. 563 564 Return: Paragraph (or None)." 565 """ 566 changed = self.get_change_element(tracked_changes=tracked_changes) 567 if not changed: 568 return None 569 return changed.get_deleted( # type: ignore 570 as_text=as_text, 571 no_header=no_header, 572 clean=clean, 573 ) 574 575 def get_inserted( 576 self, 577 as_text: bool = False, 578 no_header: bool = False, 579 clean: bool = True, 580 ) -> str | Element | list[Element] | None: 581 """Return None.""" 582 return None 583 584 def get_start(self) -> TextChangeStart | None: 585 """Return None.""" 586 return None 587 588 def get_end(self) -> TextChangeEnd | None: 589 """Return None.""" 590 return None
The TextChange "text:change" element marks a position in an empty region where text has been deleted.
554 def get_deleted( 555 self, 556 tracked_changes: Element | None = None, 557 as_text: bool = False, 558 no_header: bool = False, 559 clean: bool = True, 560 ) -> Element | None: 561 """Shortcut to get the deleted informations stored in the 562 TextDeletion stored in the tracked changes. 563 564 Return: Paragraph (or None)." 565 """ 566 changed = self.get_change_element(tracked_changes=tracked_changes) 567 if not changed: 568 return None 569 return changed.get_deleted( # type: ignore 570 as_text=as_text, 571 no_header=no_header, 572 clean=clean, 573 )
Shortcut to get the deleted informations stored in the TextDeletion stored in the tracked changes.
Return: Paragraph (or None)."
575 def get_inserted( 576 self, 577 as_text: bool = False, 578 no_header: bool = False, 579 clean: bool = True, 580 ) -> str | Element | list[Element] | None: 581 """Return None.""" 582 return None
Return None.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
593class TextChangeEnd(TextChange): 594 """The TextChangeEnd "text:change-end" element marks the end of a region 595 with content where text has been inserted or the format has been 596 changed. 597 """ 598 599 _tag = "text:change-end" 600 601 def get_start(self) -> TextChangeStart | None: 602 """Return the corresponding annotation starting tag or None.""" 603 idx = self.get_id() 604 parent = self.parent 605 if parent is None: 606 raise ValueError("Can not find end tag: no parent available.") 607 body = self.document_body 608 if not body: 609 body = self.root 610 return body.get_text_change_start(idx=idx) # type: ignore 611 612 def get_end(self) -> TextChangeEnd | None: 613 """Return self.""" 614 return self 615 616 def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None: 617 """Return None.""" 618 return None 619 620 def get_inserted( 621 self, 622 as_text: bool = False, 623 no_header: bool = False, 624 clean: bool = True, 625 ) -> str | Element | list[Element] | None: 626 """Return the content between text:change-start and text:change-end. 627 628 If no content exists (deletion tag), returns None (or '' if text flag 629 is True). 630 If as_text is True: returns the text content. 631 If clean is True: suppress unwanted tags (deletions marks, ...) 632 If no_header is True: existing text:h are changed in text:p 633 By default: returns a list of Element, cleaned and with headers 634 635 Arguments: 636 637 as_text -- boolean 638 639 clean -- boolean 640 641 no_header -- boolean 642 643 Return: list or Element or text 644 """ 645 646 # idx = self.get_id() 647 start = self.get_start() 648 end = self.get_end() 649 if end is None or start is None: 650 if as_text: 651 return "" 652 return None 653 body = self.document_body 654 if not body: 655 body = self.root 656 return body.get_between( 657 start, end, as_text=as_text, no_header=no_header, clean=clean 658 )
The TextChangeEnd "text:change-end" element marks the end of a region with content where text has been inserted or the format has been changed.
601 def get_start(self) -> TextChangeStart | None: 602 """Return the corresponding annotation starting tag or None.""" 603 idx = self.get_id() 604 parent = self.parent 605 if parent is None: 606 raise ValueError("Can not find end tag: no parent available.") 607 body = self.document_body 608 if not body: 609 body = self.root 610 return body.get_text_change_start(idx=idx) # type: ignore
Return the corresponding annotation starting tag or None.
616 def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None: 617 """Return None.""" 618 return None
Return None.
620 def get_inserted( 621 self, 622 as_text: bool = False, 623 no_header: bool = False, 624 clean: bool = True, 625 ) -> str | Element | list[Element] | None: 626 """Return the content between text:change-start and text:change-end. 627 628 If no content exists (deletion tag), returns None (or '' if text flag 629 is True). 630 If as_text is True: returns the text content. 631 If clean is True: suppress unwanted tags (deletions marks, ...) 632 If no_header is True: existing text:h are changed in text:p 633 By default: returns a list of Element, cleaned and with headers 634 635 Arguments: 636 637 as_text -- boolean 638 639 clean -- boolean 640 641 no_header -- boolean 642 643 Return: list or Element or text 644 """ 645 646 # idx = self.get_id() 647 start = self.get_start() 648 end = self.get_end() 649 if end is None or start is None: 650 if as_text: 651 return "" 652 return None 653 body = self.document_body 654 if not body: 655 body = self.root 656 return body.get_between( 657 start, end, as_text=as_text, no_header=no_header, clean=clean 658 )
Return the content between text:change-start and text:change-end.
If no content exists (deletion tag), returns None (or '' if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
661class TextChangeStart(TextChangeEnd): 662 """The TextChangeStart "text:change-start" element marks the start of a 663 region with content where text has been inserted or the format has 664 been changed. 665 """ 666 667 _tag = "text:change-start" 668 669 def get_start(self) -> TextChangeStart: 670 """Return self.""" 671 return self 672 673 def get_end(self) -> TextChangeEnd: 674 """Return the corresponding change-end tag or None.""" 675 idx = self.get_id() 676 parent = self.parent 677 if parent is None: 678 raise ValueError("Can not find end tag: no parent available.") 679 body = self.document_body 680 if not body: 681 body = self.root 682 return body.get_text_change_end(idx=idx) # type: ignore 683 684 def delete( 685 self, 686 child: Element | None = None, 687 keep_tail: bool = True, 688 ) -> None: 689 """Delete the given element from the XML tree. If no element is given, 690 "self" is deleted. The XML library may allow to continue to use an 691 element now "orphan" as long as you have a reference to it. 692 693 For TextChangeStart : delete also the end tag if exists. 694 695 Arguments: 696 697 child -- Element 698 699 keep_tail -- boolean (default to True), True for most usages. 700 """ 701 if child is not None: # act like normal delete 702 return super().delete(child, keep_tail) 703 idx = self.get_id() 704 parent = self.parent 705 if parent is None: 706 raise ValueError("cannot delete the root element") 707 body = self.document_body 708 if not body: 709 body = parent 710 end = body.get_text_change_end(idx=idx) 711 if end: 712 end.delete() 713 # act like normal delete 714 super().delete()
The TextChangeStart "text:change-start" element marks the start of a region with content where text has been inserted or the format has been changed.
673 def get_end(self) -> TextChangeEnd: 674 """Return the corresponding change-end tag or None.""" 675 idx = self.get_id() 676 parent = self.parent 677 if parent is None: 678 raise ValueError("Can not find end tag: no parent available.") 679 body = self.document_body 680 if not body: 681 body = self.root 682 return body.get_text_change_end(idx=idx) # type: ignore
Return the corresponding change-end tag or None.
684 def delete( 685 self, 686 child: Element | None = None, 687 keep_tail: bool = True, 688 ) -> None: 689 """Delete the given element from the XML tree. If no element is given, 690 "self" is deleted. The XML library may allow to continue to use an 691 element now "orphan" as long as you have a reference to it. 692 693 For TextChangeStart : delete also the end tag if exists. 694 695 Arguments: 696 697 child -- Element 698 699 keep_tail -- boolean (default to True), True for most usages. 700 """ 701 if child is not None: # act like normal delete 702 return super().delete(child, keep_tail) 703 idx = self.get_id() 704 parent = self.parent 705 if parent is None: 706 raise ValueError("cannot delete the root element") 707 body = self.document_body 708 if not body: 709 body = parent 710 end = body.get_text_change_end(idx=idx) 711 if end: 712 end.delete() 713 # act like normal delete 714 super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For TextChangeStart : delete also the end tag if exists.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
358class TextChangedRegion(Element): 359 """Each TextChangedRegion "text:changed-region" element contains a single 360 element, one of TextInsertion, TextDeletion or TextFormatChange that 361 corresponds to a change being tracked within the scope of the 362 "text:tracked-changes" element that contains the "text:changed-region" 363 instance. 364 The xml:id attribute of the TextChangedRegion is referenced 365 from the "text:change", "text:change-start" and "text:change-end" 366 elements that identify where the change applies to markup in the scope of 367 the "text:tracked-changes" element. 368 369 Warning : for this implementation, text:change should be referenced only 370 once in the scope, which is different from ODF 1.2 requirement: 371 " A "text:changed-region" can be referenced by more than one 372 change, but the corresponding referencing change mark elements 373 shall be of the same change type - insertion, format change or 374 deletion. " 375 """ 376 377 _tag = "text:changed-region" 378 379 def get_change_info(self) -> Element | None: 380 """Shortcut to get the ChangeInfo element of the change 381 element child. 382 383 Return: ChangeInfo element. 384 """ 385 return self.get_element("descendant::office:change-info") 386 387 def set_change_info( 388 self, 389 change_info: Element | None = None, 390 creator: str | None = None, 391 date: datetime | None = None, 392 comments: Element | list[Element] | None = None, 393 ) -> None: 394 """Shortcut to set the ChangeInfo element of the sub change element. 395 See TextInsertion.set_change_info() for details. 396 397 Arguments: 398 399 change_info -- ChangeInfo element (or None) 400 401 cretor -- str (or None) 402 403 date -- datetime (or None) 404 405 comments -- Paragraph or list of Paragraph elements (or None) 406 """ 407 child = self.get_change_element() 408 if not child: 409 raise ValueError 410 child.set_change_info( # type: ignore 411 change_info=change_info, creator=creator, date=date, comments=comments 412 ) 413 414 def get_change_element(self) -> Element | None: 415 """Get the change element child. It can be either: TextInsertion, 416 TextDeletion, or TextFormatChange as an Element object. 417 418 Return: Element. 419 """ 420 request = ( 421 "descendant::text:insertion " 422 "| descendant::text:deletion" 423 "| descendant::text:format-change" 424 ) 425 return self._filtered_element(request, 0) 426 427 def _get_text_id(self) -> str | None: 428 return self.get_attribute_string("text:id") 429 430 def _set_text_id(self, text_id: str) -> None: 431 self.set_attribute("text:id", text_id) 432 433 def _get_xml_id(self) -> str | None: 434 return self.get_attribute_string("xml:id") 435 436 def _set_xml_id(self, xml_id: str) -> None: 437 self.set_attribute("xml:id", xml_id) 438 439 def get_id(self) -> str | None: 440 """Get the "text:id" attribute. 441 442 Return: str 443 """ 444 return self._get_text_id() 445 446 def set_id(self, idx: str) -> None: 447 """Set both the "text:id" and "xml:id" attributes with same value.""" 448 self._set_text_id(idx) 449 self._set_xml_id(idx)
Each TextChangedRegion "text:changed-region" element contains a single element, one of TextInsertion, TextDeletion or TextFormatChange that corresponds to a change being tracked within the scope of the "text:tracked-changes" element that contains the "text:changed-region" instance. The xml:id attribute of the TextChangedRegion is referenced from the "text:change", "text:change-start" and "text:change-end" elements that identify where the change applies to markup in the scope of the "text:tracked-changes" element.
Warning : for this implementation, text:change should be referenced only once in the scope, which is different from ODF 1.2 requirement: " A "text:changed-region" can be referenced by more than one change, but the corresponding referencing change mark elements shall be of the same change type - insertion, format change or deletion. "
379 def get_change_info(self) -> Element | None: 380 """Shortcut to get the ChangeInfo element of the change 381 element child. 382 383 Return: ChangeInfo element. 384 """ 385 return self.get_element("descendant::office:change-info")
Shortcut to get the ChangeInfo element of the change element child.
Return: ChangeInfo element.
387 def set_change_info( 388 self, 389 change_info: Element | None = None, 390 creator: str | None = None, 391 date: datetime | None = None, 392 comments: Element | list[Element] | None = None, 393 ) -> None: 394 """Shortcut to set the ChangeInfo element of the sub change element. 395 See TextInsertion.set_change_info() for details. 396 397 Arguments: 398 399 change_info -- ChangeInfo element (or None) 400 401 cretor -- str (or None) 402 403 date -- datetime (or None) 404 405 comments -- Paragraph or list of Paragraph elements (or None) 406 """ 407 child = self.get_change_element() 408 if not child: 409 raise ValueError 410 child.set_change_info( # type: ignore 411 change_info=change_info, creator=creator, date=date, comments=comments 412 )
Shortcut to set the ChangeInfo element of the sub change element. See TextInsertion.set_change_info() for details.
Arguments:
change_info -- ChangeInfo element (or None)
cretor -- str (or None)
date -- datetime (or None)
comments -- Paragraph or list of Paragraph elements (or None)
414 def get_change_element(self) -> Element | None: 415 """Get the change element child. It can be either: TextInsertion, 416 TextDeletion, or TextFormatChange as an Element object. 417 418 Return: Element. 419 """ 420 request = ( 421 "descendant::text:insertion " 422 "| descendant::text:deletion" 423 "| descendant::text:format-change" 424 ) 425 return self._filtered_element(request, 0)
Get the change element child. It can be either: TextInsertion, TextDeletion, or TextFormatChange as an Element object.
Return: Element.
439 def get_id(self) -> str | None: 440 """Get the "text:id" attribute. 441 442 Return: str 443 """ 444 return self._get_text_id()
Get the "text:id" attribute.
Return: str
446 def set_id(self, idx: str) -> None: 447 """Set both the "text:id" and "xml:id" attributes with same value.""" 448 self._set_text_id(idx) 449 self._set_xml_id(idx)
Set both the "text:id" and "xml:id" attributes with same value.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
251class TextDeletion(TextInsertion): 252 """The TextDeletion "text:deletion" contains information that identifies 253 the person responsible for a deletion and the date of that deletion. 254 This information may also contain one or more Paragraph which contains 255 a comment on the deletion. The TextDeletion element may also contain 256 content that was deleted while change tracking was enabled. The position 257 where the text was deleted is marked by a "text:change" element. Deleted 258 text is contained in a paragraph element. To reconstruct the original 259 text, the paragraph containing the deleted text is merged with its 260 surrounding paragraph or heading element. To reconstruct the text before 261 a deletion took place: 262 - If the change mark is inside a paragraph, insert the content that was 263 deleted, but remove all leading start tags up to and including the 264 first "text:p" element and all trailing end tags up to and including 265 the last "/text:p" or "/text:h" element. If the last trailing element 266 is a "/text:h", change the end tag "/text:p" following this insertion 267 to a "/text:h" element. 268 - If the change mark is inside a heading, insert the content that was 269 deleted, but remove all leading start tags up to and including the 270 first "text:h" element and all trailing end tags up to and including 271 the last "/text:h" or "/text:p" element. If the last trailing element 272 is a "/text:p", change the end tag "/text:h" following this insertion 273 to a "/text:p" element. 274 - Otherwise, copy the text content of the "text:deletion" element in 275 place of the change mark. 276 """ 277 278 _tag = "text:deletion" 279 280 def get_deleted( 281 self, 282 as_text: bool = False, 283 no_header: bool = False, 284 ) -> str | list[Element] | None: 285 """Get the deleted informations stored in the TextDeletion. 286 If as_text is True: returns the text content. 287 If no_header is True: existing Heading are changed in Paragraph 288 289 Arguments: 290 291 as_text -- boolean 292 293 no_header -- boolean 294 295 Return: Paragraph and Header list 296 """ 297 children = self.children 298 inner = [elem for elem in children if elem.tag != "office:change-info"] 299 if no_header: # crude replace t:h by t:p 300 new_inner = [] 301 for element in inner: 302 if element.tag == "text:h": 303 children = element.children 304 text = element.text 305 para = Element.from_tag("text:p") 306 para.text = text 307 for child in children: 308 para.append(child) 309 new_inner.append(para) 310 else: 311 new_inner.append(element) 312 inner = new_inner 313 if as_text: 314 return "\n".join([elem.get_formatted_text(context=None) for elem in inner]) 315 return inner 316 317 def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None: 318 """Set the deleted informations stored in the TextDeletion. An 319 actual content that was deleted is expected, embeded in a Paragraph 320 element or Header. 321 322 Arguments: 323 324 paragraph_or_list -- Paragraph or Header element (or list) 325 """ 326 for element in self.get_deleted(): # type: ignore 327 self.delete(element) # type: ignore 328 if isinstance(paragraph_or_list, Element): 329 paragraph_or_list = [paragraph_or_list] 330 for element in paragraph_or_list: 331 self.append(element) 332 333 def get_inserted( 334 self, 335 as_text: bool = False, 336 no_header: bool = False, 337 clean: bool = True, 338 ) -> str | Element | list[Element] | None: 339 """Return None.""" 340 if as_text: 341 return "" 342 return None
The TextDeletion "text:deletion" contains information that identifies the person responsible for a deletion and the date of that deletion. This information may also contain one or more Paragraph which contains a comment on the deletion. The TextDeletion element may also contain content that was deleted while change tracking was enabled. The position where the text was deleted is marked by a "text:change" element. Deleted text is contained in a paragraph element. To reconstruct the original text, the paragraph containing the deleted text is merged with its surrounding paragraph or heading element. To reconstruct the text before a deletion took place:
- If the change mark is inside a paragraph, insert the content that was deleted, but remove all leading start tags up to and including the first "text:p" element and all trailing end tags up to and including the last "/text:p" or "/text:h" element. If the last trailing element is a "/text:h", change the end tag "/text:p" following this insertion to a "/text:h" element.
- If the change mark is inside a heading, insert the content that was deleted, but remove all leading start tags up to and including the first "text:h" element and all trailing end tags up to and including the last "/text:h" or "/text:p" element. If the last trailing element is a "/text:p", change the end tag "/text:h" following this insertion to a "/text:p" element.
- Otherwise, copy the text content of the "text:deletion" element in place of the change mark.
280 def get_deleted( 281 self, 282 as_text: bool = False, 283 no_header: bool = False, 284 ) -> str | list[Element] | None: 285 """Get the deleted informations stored in the TextDeletion. 286 If as_text is True: returns the text content. 287 If no_header is True: existing Heading are changed in Paragraph 288 289 Arguments: 290 291 as_text -- boolean 292 293 no_header -- boolean 294 295 Return: Paragraph and Header list 296 """ 297 children = self.children 298 inner = [elem for elem in children if elem.tag != "office:change-info"] 299 if no_header: # crude replace t:h by t:p 300 new_inner = [] 301 for element in inner: 302 if element.tag == "text:h": 303 children = element.children 304 text = element.text 305 para = Element.from_tag("text:p") 306 para.text = text 307 for child in children: 308 para.append(child) 309 new_inner.append(para) 310 else: 311 new_inner.append(element) 312 inner = new_inner 313 if as_text: 314 return "\n".join([elem.get_formatted_text(context=None) for elem in inner]) 315 return inner
Get the deleted informations stored in the TextDeletion. If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph
Arguments:
as_text -- boolean
no_header -- boolean
Return: Paragraph and Header list
317 def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None: 318 """Set the deleted informations stored in the TextDeletion. An 319 actual content that was deleted is expected, embeded in a Paragraph 320 element or Header. 321 322 Arguments: 323 324 paragraph_or_list -- Paragraph or Header element (or list) 325 """ 326 for element in self.get_deleted(): # type: ignore 327 self.delete(element) # type: ignore 328 if isinstance(paragraph_or_list, Element): 329 paragraph_or_list = [paragraph_or_list] 330 for element in paragraph_or_list: 331 self.append(element)
Set the deleted informations stored in the TextDeletion. An actual content that was deleted is expected, embeded in a Paragraph element or Header.
Arguments:
paragraph_or_list -- Paragraph or Header element (or list)
333 def get_inserted( 334 self, 335 as_text: bool = False, 336 no_header: bool = False, 337 clean: bool = True, 338 ) -> str | Element | list[Element] | None: 339 """Return None.""" 340 if as_text: 341 return "" 342 return None
Return None.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
345class TextFormatChange(TextInsertion): 346 """The TextFormatChange "text:format-change" element represents any change 347 in formatting attributes. The region where the change took place is 348 marked by "text:change-start", "text:change-end" or "text:change" 349 elements. 350 351 Note: This element does not contain formatting changes that have taken 352 place. 353 """ 354 355 _tag = "text:format-change"
The TextFormatChange "text:format-change" element represents any change in formatting attributes. The region where the change took place is marked by "text:change-start", "text:change-end" or "text:change" elements.
Note: This element does not contain formatting changes that have taken place.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
134class TextInsertion(Element): 135 """The TextInsertion "text:insertion" element contains the information 136 that identifies the person responsible for a change and the date of 137 that change. This information may also contain one or more "text:p" 138 Paragraph which contain a comment on the insertion. The 139 TextInsertion element's parent "text:changed-region" element has an 140 xml:id or text:id attribute, the value of which binds that parent 141 element to the text:change-id attribute on the "text:change-start" 142 and "text:change-end" elements. 143 """ 144 145 _tag = "text:insertion" 146 147 def get_deleted( 148 self, 149 as_text: bool = False, 150 no_header: bool = False, 151 ) -> str | list[Element] | None: 152 """Return: None.""" 153 if as_text: 154 return "" 155 return None 156 157 def get_inserted( 158 self, 159 as_text: bool = False, 160 no_header: bool = False, 161 clean: bool = True, 162 ) -> str | Element | list[Element] | None: 163 """Shortcut to text:change-start.get_inserted(). Return the content 164 between text:change-start and text:change-end. 165 166 If as_text is True: returns the text content. 167 If no_header is True: existing Heading are changed in Paragraph 168 If no_header is True: existing text:h are changed in text:p 169 By default: returns a list of Element, cleaned and with headers 170 171 Arguments: 172 173 as_text -- boolean 174 175 clean -- boolean 176 177 no_header -- boolean 178 179 Return: list or Element or text 180 """ 181 current = self.parent # text:changed-region 182 if not current: 183 raise ValueError 184 idx = current.get_id() # type: ignore 185 body = self.document_body 186 if not body: 187 body = self.root 188 text_change = body.get_text_change_start(idx=idx) 189 if not text_change: 190 raise ValueError 191 return text_change.get_inserted( # type: ignore 192 as_text=as_text, no_header=no_header, clean=clean 193 ) 194 195 def get_change_info(self) -> Element | None: 196 """Get the ChangeInfo child of the element. 197 198 Return: ChangeInfo element. 199 """ 200 return self.get_element("descendant::office:change-info") 201 202 def set_change_info( 203 self, 204 change_info: Element | None = None, 205 creator: str | None = None, 206 date: datetime | None = None, 207 comments: Element | list[Element] | None = None, 208 ) -> None: 209 """Set the ChangeInfo element for the change element. If change_info 210 is not provided, creator, date and comments will be used to build a 211 suitable change info element. Default for creator is 'Unknown', 212 default for date is current time and default for comments is no 213 comment at all. 214 The new change info element will replace any existant ChangeInfo. 215 216 Arguments: 217 218 change_info -- ChangeInfo element (or None) 219 220 cretor -- str (or None) 221 222 date -- datetime (or None) 223 224 comments -- Paragraph or list of Paragraph elements (or None) 225 """ 226 if change_info is None: 227 new_change_info = ChangeInfo(creator, date) 228 if comments is not None: 229 if isinstance(comments, Element): 230 # single pararagraph comment 231 comments_list = [comments] 232 else: 233 comments_list = comments 234 # assume iterable of Paragraph 235 for paragraph in comments_list: 236 if not isinstance(paragraph, Paragraph): 237 raise TypeError(f"Not a Paragraph: '{paragraph!r}'") 238 new_change_info.insert(paragraph, xmlposition=LAST_CHILD) 239 else: 240 if not isinstance(change_info, ChangeInfo): 241 raise TypeError(f"Not a ChangeInfo: '{change_info!r}'") 242 new_change_info = change_info 243 244 old = self.get_change_info() 245 if old is not None: 246 self.replace_element(old, new_change_info) 247 else: 248 self.insert(new_change_info, xmlposition=FIRST_CHILD)
The TextInsertion "text:insertion" element contains the information that identifies the person responsible for a change and the date of that change. This information may also contain one or more "text:p" Paragraph which contain a comment on the insertion. The TextInsertion element's parent "text:changed-region" element has an xml:id or text:id attribute, the value of which binds that parent element to the text:change-id attribute on the "text:change-start" and "text:change-end" elements.
147 def get_deleted( 148 self, 149 as_text: bool = False, 150 no_header: bool = False, 151 ) -> str | list[Element] | None: 152 """Return: None.""" 153 if as_text: 154 return "" 155 return None
Return: None.
157 def get_inserted( 158 self, 159 as_text: bool = False, 160 no_header: bool = False, 161 clean: bool = True, 162 ) -> str | Element | list[Element] | None: 163 """Shortcut to text:change-start.get_inserted(). Return the content 164 between text:change-start and text:change-end. 165 166 If as_text is True: returns the text content. 167 If no_header is True: existing Heading are changed in Paragraph 168 If no_header is True: existing text:h are changed in text:p 169 By default: returns a list of Element, cleaned and with headers 170 171 Arguments: 172 173 as_text -- boolean 174 175 clean -- boolean 176 177 no_header -- boolean 178 179 Return: list or Element or text 180 """ 181 current = self.parent # text:changed-region 182 if not current: 183 raise ValueError 184 idx = current.get_id() # type: ignore 185 body = self.document_body 186 if not body: 187 body = self.root 188 text_change = body.get_text_change_start(idx=idx) 189 if not text_change: 190 raise ValueError 191 return text_change.get_inserted( # type: ignore 192 as_text=as_text, no_header=no_header, clean=clean 193 )
Shortcut to text:change-start.get_inserted(). Return the content between text:change-start and text:change-end.
If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text
195 def get_change_info(self) -> Element | None: 196 """Get the ChangeInfo child of the element. 197 198 Return: ChangeInfo element. 199 """ 200 return self.get_element("descendant::office:change-info")
Get the ChangeInfo child of the element.
Return: ChangeInfo element.
202 def set_change_info( 203 self, 204 change_info: Element | None = None, 205 creator: str | None = None, 206 date: datetime | None = None, 207 comments: Element | list[Element] | None = None, 208 ) -> None: 209 """Set the ChangeInfo element for the change element. If change_info 210 is not provided, creator, date and comments will be used to build a 211 suitable change info element. Default for creator is 'Unknown', 212 default for date is current time and default for comments is no 213 comment at all. 214 The new change info element will replace any existant ChangeInfo. 215 216 Arguments: 217 218 change_info -- ChangeInfo element (or None) 219 220 cretor -- str (or None) 221 222 date -- datetime (or None) 223 224 comments -- Paragraph or list of Paragraph elements (or None) 225 """ 226 if change_info is None: 227 new_change_info = ChangeInfo(creator, date) 228 if comments is not None: 229 if isinstance(comments, Element): 230 # single pararagraph comment 231 comments_list = [comments] 232 else: 233 comments_list = comments 234 # assume iterable of Paragraph 235 for paragraph in comments_list: 236 if not isinstance(paragraph, Paragraph): 237 raise TypeError(f"Not a Paragraph: '{paragraph!r}'") 238 new_change_info.insert(paragraph, xmlposition=LAST_CHILD) 239 else: 240 if not isinstance(change_info, ChangeInfo): 241 raise TypeError(f"Not a ChangeInfo: '{change_info!r}'") 242 new_change_info = change_info 243 244 old = self.get_change_info() 245 if old is not None: 246 self.replace_element(old, new_change_info) 247 else: 248 self.insert(new_change_info, xmlposition=FIRST_CHILD)
Set the ChangeInfo element for the change element. If change_info is not provided, creator, date and comments will be used to build a suitable change info element. Default for creator is 'Unknown', default for date is current time and default for comments is no comment at all. The new change info element will replace any existant ChangeInfo.
Arguments:
change_info -- ChangeInfo element (or None)
cretor -- str (or None)
date -- datetime (or None)
comments -- Paragraph or list of Paragraph elements (or None)
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
467class TocEntryTemplate(Element): 468 """ODF "text:table-of-content-entry-template" 469 470 Arguments: 471 472 style -- str 473 """ 474 475 _tag = "text:table-of-content-entry-template" 476 _properties = (PropDef("style", "text:style-name"),) 477 478 def __init__( 479 self, 480 style: str | None = None, 481 outline_level: int | None = None, 482 **kwargs: Any, 483 ) -> None: 484 super().__init__(**kwargs) 485 if self._do_init: 486 if style: 487 self.style = style 488 if outline_level: 489 self.outline_level = outline_level 490 491 @property 492 def outline_level(self) -> int | None: 493 return self.get_attribute_integer("text:outline-level") 494 495 @outline_level.setter 496 def outline_level(self, level: int) -> None: 497 self.set_attribute("text:outline-level", str(level)) 498 499 def complete_defaults(self) -> None: 500 self.append(Element.from_tag("text:index-entry-chapter")) 501 self.append(Element.from_tag("text:index-entry-text")) 502 self.append(Element.from_tag("text:index-entry-text")) 503 ts = Element.from_tag("text:index-entry-text") 504 ts.set_style_attribute("style:type", "right") 505 ts.set_style_attribute("style:leader-char", ".") 506 self.append(ts) 507 self.append(Element.from_tag("text:index-entry-page-number"))
ODF "text:table-of-content-entry-template"
Arguments:
style -- str
499 def complete_defaults(self) -> None: 500 self.append(Element.from_tag("text:index-entry-chapter")) 501 self.append(Element.from_tag("text:index-entry-text")) 502 self.append(Element.from_tag("text:index-entry-text")) 503 ts = Element.from_tag("text:index-entry-text") 504 ts.set_style_attribute("style:type", "right") 505 ts.set_style_attribute("style:leader-char", ".") 506 self.append(ts) 507 self.append(Element.from_tag("text:index-entry-page-number"))
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
452class TrackedChanges(Element): 453 """The TrackedChanges "text:tracked-changes" element acts as a container 454 for TextChangedRegion elements that represent changes in a certain 455 scope of an OpenDocument document. This scope is the element in which 456 the TrackedChanges element occurs. Changes in this scope shall be 457 tracked by TextChangedRegion elements contained in the 458 TrackedChanges element in this scope. If a TrackedChanges 459 element is absent, there are no tracked changes in the corresponding 460 scope. In this case, all change mark elements in this scope shall be 461 ignored. 462 """ 463 464 _tag = "text:tracked-changes" 465 466 def get_changed_regions( 467 self, 468 creator: str | None = None, 469 date: datetime | None = None, 470 content: str | None = None, 471 role: str | None = None, 472 ) -> list[Element]: 473 changed_regions = self._filtered_elements( 474 "text:changed-region", 475 dc_creator=creator, 476 dc_date=date, 477 content=content, 478 ) 479 if role is None: 480 return changed_regions 481 result: list[Element] = [] 482 for regien in changed_regions: 483 changed = regien.get_change_element() # type: ignore 484 if not changed: 485 continue 486 if changed.tag.endswith(role): 487 result.append(regien) 488 return result 489 490 def get_changed_region( 491 self, 492 position: int = 0, 493 text_id: str | None = None, 494 creator: str | None = None, 495 date: datetime | None = None, 496 content: str | None = None, 497 ) -> Element | None: 498 return self._filtered_element( 499 "text:changed-region", 500 position, 501 text_id=text_id, 502 dc_creator=creator, 503 dc_date=date, 504 content=content, 505 )
The TrackedChanges "text:tracked-changes" element acts as a container for TextChangedRegion elements that represent changes in a certain scope of an OpenDocument document. This scope is the element in which the TrackedChanges element occurs. Changes in this scope shall be tracked by TextChangedRegion elements contained in the TrackedChanges element in this scope. If a TrackedChanges element is absent, there are no tracked changes in the corresponding scope. In this case, all change mark elements in this scope shall be ignored.
466 def get_changed_regions( 467 self, 468 creator: str | None = None, 469 date: datetime | None = None, 470 content: str | None = None, 471 role: str | None = None, 472 ) -> list[Element]: 473 changed_regions = self._filtered_elements( 474 "text:changed-region", 475 dc_creator=creator, 476 dc_date=date, 477 content=content, 478 ) 479 if role is None: 480 return changed_regions 481 result: list[Element] = [] 482 for regien in changed_regions: 483 changed = regien.get_change_element() # type: ignore 484 if not changed: 485 continue 486 if changed.tag.endswith(role): 487 result.append(regien) 488 return result
490 def get_changed_region( 491 self, 492 position: int = 0, 493 text_id: str | None = None, 494 creator: str | None = None, 495 date: datetime | None = None, 496 content: str | None = None, 497 ) -> Element | None: 498 return self._filtered_element( 499 "text:changed-region", 500 position, 501 text_id=text_id, 502 dc_creator=creator, 503 dc_date=date, 504 content=content, 505 )
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
212class UserDefined(ElementTyped): 213 """Return a user defined field "text:user-defined". If the current 214 document is provided, try to extract the content of the meta user defined 215 field of same name. 216 217 Arguments: 218 219 name -- str, name of the user defined field 220 221 value -- python typed value, value of the field 222 223 value_type -- str, office:value-type known type 224 225 text -- str 226 227 style -- str 228 229 from_document -- ODF document 230 """ 231 232 _tag = "text:user-defined" 233 _properties = ( 234 PropDef("name", "text:name"), 235 PropDef("style", "style:data-style-name"), 236 ) 237 238 def __init__( 239 self, 240 name: str = "", 241 value: Any = None, 242 value_type: str | None = None, 243 text: str | None = None, 244 style: str | None = None, 245 from_document: Document | None = None, 246 **kwargs: Any, 247 ) -> None: 248 super().__init__(**kwargs) 249 if self._do_init: 250 if name: 251 self.name = name 252 if style: 253 self.style = style 254 if from_document is not None: 255 meta_infos = from_document.meta 256 content = meta_infos.get_user_defined_metadata_of_name(name) 257 if content is not None: 258 value = content.get("value", None) 259 value_type = content.get("value_type", None) 260 text = content.get("text", None) 261 text = self.set_value_and_type( 262 value=value, value_type=value_type, text=text 263 ) 264 self.text = text # type: ignore
Return a user defined field "text:user-defined". If the current document is provided, try to extract the content of the meta user defined field of same name.
Arguments:
name -- str, name of the user defined field
value -- python typed value, value of the field
value_type -- str, office:value-type known type
text -- str
style -- str
from_document -- ODF document
238 def __init__( 239 self, 240 name: str = "", 241 value: Any = None, 242 value_type: str | None = None, 243 text: str | None = None, 244 style: str | None = None, 245 from_document: Document | None = None, 246 **kwargs: Any, 247 ) -> None: 248 super().__init__(**kwargs) 249 if self._do_init: 250 if name: 251 self.name = name 252 if style: 253 self.style = style 254 if from_document is not None: 255 meta_infos = from_document.meta 256 content = meta_infos.get_user_defined_metadata_of_name(name) 257 if content is not None: 258 value = content.get("value", None) 259 value_type = content.get("value_type", None) 260 text = content.get("text", None) 261 text = self.set_value_and_type( 262 value=value, value_type=value_type, text=text 263 ) 264 self.text = text # type: ignore
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
146class UserFieldDecl(ElementTyped): 147 _tag = "text:user-field-decl" 148 _properties = (PropDef("name", "text:name"),) 149 150 def __init__( 151 self, 152 name: str | None = None, 153 value: Any = None, 154 value_type: str | None = None, 155 **kwargs: Any, 156 ) -> None: 157 super().__init__(**kwargs) 158 if self._do_init: 159 if name: 160 self.name = name 161 self.set_value_and_type(value=value, value_type=value_type) 162 163 def set_value(self, value: Any) -> None: 164 name = self.get_attribute("text:name") 165 self.clear() 166 self.set_value_and_type(value=value) 167 self.set_attribute("text:name", name)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
173class UserFieldGet(ElementTyped): 174 _tag = "text:user-field-get" 175 _properties = ( 176 PropDef("name", "text:name"), 177 PropDef("style", "style:data-style-name"), 178 ) 179 180 def __init__( 181 self, 182 name: str | None = None, 183 value: Any = None, 184 value_type: str | None = None, 185 text: str | None = None, 186 style: str | None = None, 187 **kwargs: Any, 188 ) -> None: 189 super().__init__(**kwargs) 190 if self._do_init: 191 if name: 192 self.name = name 193 text = self.set_value_and_type( 194 value=value, value_type=value_type, text=text 195 ) 196 self.text = text # type: ignore 197 198 if style: 199 self.style = style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
180 def __init__( 181 self, 182 name: str | None = None, 183 value: Any = None, 184 value_type: str | None = None, 185 text: str | None = None, 186 style: str | None = None, 187 **kwargs: Any, 188 ) -> None: 189 super().__init__(**kwargs) 190 if self._do_init: 191 if name: 192 self.name = name 193 text = self.set_value_and_type( 194 value=value, value_type=value_type, text=text 195 ) 196 self.text = text # type: ignore 197 198 if style: 199 self.style = style
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
376class VarChapter(Element): 377 _tag = "text:chapter" 378 _properties = ( 379 PropDef("display", "text:display"), 380 PropDef("outline_level", "text:outline-level"), 381 ) 382 DISPLAY_VALUE_CHOICE = { # noqa: RUF012 383 "number", 384 "name", 385 "number-and-name", 386 "plain-number", 387 "plain-number-and-name", 388 } 389 390 def __init__( 391 self, 392 display: str | None = "name", 393 outline_level: str | None = None, 394 **kwargs: Any, 395 ) -> None: 396 """display can be: 'number', 'name', 'number-and-name', 'plain-number' or 397 'plain-number-and-name' 398 """ 399 super().__init__(**kwargs) 400 if self._do_init: 401 if display not in VarChapter.DISPLAY_VALUE_CHOICE: 402 raise ValueError("unknown display value: %s" % display) 403 self.display = display 404 if outline_level is not None: 405 self.outline_level = outline_level
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
390 def __init__( 391 self, 392 display: str | None = "name", 393 outline_level: str | None = None, 394 **kwargs: Any, 395 ) -> None: 396 """display can be: 'number', 'name', 'number-and-name', 'plain-number' or 397 'plain-number-and-name' 398 """ 399 super().__init__(**kwargs) 400 if self._do_init: 401 if display not in VarChapter.DISPLAY_VALUE_CHOICE: 402 raise ValueError("unknown display value: %s" % display) 403 self.display = display 404 if outline_level is not None: 405 self.outline_level = outline_level
display can be: 'number', 'name', 'number-and-name', 'plain-number' or 'plain-number-and-name'
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
456class VarCreationDate(Element): 457 _tag = "text:creation-date" 458 _properties = ( 459 PropDef("fixed", "text:fixed"), 460 PropDef("data_style", "style:data-style-name"), 461 ) 462 463 def __init__( 464 self, 465 fixed: bool = False, 466 data_style: str | None = None, 467 **kwargs: Any, 468 ) -> None: 469 super().__init__(**kwargs) 470 if self._do_init: 471 if fixed: 472 self.fixed = True 473 if data_style: 474 self.data_style = data_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
480class VarCreationTime(Element): 481 _tag = "text:creation-time" 482 _properties = ( 483 PropDef("fixed", "text:fixed"), 484 PropDef("data_style", "style:data-style-name"), 485 ) 486 487 def __init__( 488 self, 489 fixed: bool = False, 490 data_style: str | None = None, 491 **kwargs: Any, 492 ) -> None: 493 super().__init__(**kwargs) 494 if self._do_init: 495 if fixed: 496 self.fixed = True 497 if data_style: 498 self.data_style = data_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
305class VarDate(Element): 306 _tag = "text:date" 307 _properties = ( 308 PropDef("date", "text:date-value"), 309 PropDef("fixed", "text:fixed"), 310 PropDef("data_style", "style:data-style-name"), 311 PropDef("date_adjust", "text:date-adjust"), 312 ) 313 314 def __init__( 315 self, 316 date: datetime | None = None, 317 fixed: bool = False, 318 data_style: str | None = None, 319 text: str | None = None, 320 date_adjust: timedelta | None = None, 321 **kwargs: Any, 322 ) -> None: 323 super().__init__(**kwargs) 324 if self._do_init: 325 if date: 326 self.date = DateTime.encode(date) 327 if fixed: 328 self.fixed = True 329 if data_style is not None: 330 self.data_style = data_style 331 if text is None and date is not None: 332 text = Date.encode(date) 333 self.text = text # type: ignore 334 if date_adjust is not None: 335 self.date_adjust = Duration.encode(date_adjust)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
314 def __init__( 315 self, 316 date: datetime | None = None, 317 fixed: bool = False, 318 data_style: str | None = None, 319 text: str | None = None, 320 date_adjust: timedelta | None = None, 321 **kwargs: Any, 322 ) -> None: 323 super().__init__(**kwargs) 324 if self._do_init: 325 if date: 326 self.date = DateTime.encode(date) 327 if fixed: 328 self.fixed = True 329 if data_style is not None: 330 self.data_style = data_style 331 if text is None and date is not None: 332 text = Date.encode(date) 333 self.text = text # type: ignore 334 if date_adjust is not None: 335 self.date_adjust = Duration.encode(date_adjust)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
40class VarDecl(Element): 41 _tag = "text:variable-decl" 42 _properties = ( 43 PropDef("name", "text:name"), 44 PropDef("value_type", "office:value-type"), 45 ) 46 47 def __init__( 48 self, 49 name: str | None = None, 50 value_type: str | None = None, 51 **kwargs: Any, 52 ) -> None: 53 super().__init__(**kwargs) 54 if self._do_init: 55 if name: 56 self.name = name 57 if value_type: 58 self.value_type = value_type
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
411class VarFileName(Element): 412 _tag = "text:file-name" 413 _properties = ( 414 PropDef("display", "text:display"), 415 PropDef("fixed", "text:fixed"), 416 ) 417 DISPLAY_VALUE_CHOICE = { # noqa: RUF012 418 "full", 419 "path", 420 "name", 421 "name-and-extension", 422 } 423 424 def __init__( 425 self, 426 display: str | None = "full", 427 fixed: bool = False, 428 **kwargs: Any, 429 ) -> None: 430 """display can be: 'full', 'path', 'name' or 'name-and-extension'""" 431 super().__init__(**kwargs) 432 if self._do_init: 433 if display not in VarFileName.DISPLAY_VALUE_CHOICE: 434 raise ValueError("unknown display value: %s" % display) 435 self.display = display 436 if fixed: 437 self.fixed = True
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
424 def __init__( 425 self, 426 display: str | None = "full", 427 fixed: bool = False, 428 **kwargs: Any, 429 ) -> None: 430 """display can be: 'full', 'path', 'name' or 'name-and-extension'""" 431 super().__init__(**kwargs) 432 if self._do_init: 433 if display not in VarFileName.DISPLAY_VALUE_CHOICE: 434 raise ValueError("unknown display value: %s" % display) 435 self.display = display 436 if fixed: 437 self.fixed = True
display can be: 'full', 'path', 'name' or 'name-and-extension'
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
111class VarGet(ElementTyped): 112 _tag = "text:variable-get" 113 _properties = ( 114 PropDef("name", "text:name"), 115 PropDef("style", "style:data-style-name"), 116 ) 117 118 def __init__( 119 self, 120 name: str | None = None, 121 value: Any = None, 122 value_type: str | None = None, 123 text: str | None = None, 124 style: str | None = None, 125 **kwargs: Any, 126 ) -> None: 127 super().__init__(**kwargs) 128 if self._do_init: 129 if name: 130 self.name = name 131 if style: 132 self.style = style 133 text = self.set_value_and_type( 134 value=value, value_type=value_type, text=text 135 ) 136 self.text = text # type: ignore
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
118 def __init__( 119 self, 120 name: str | None = None, 121 value: Any = None, 122 value_type: str | None = None, 123 text: str | None = None, 124 style: str | None = None, 125 **kwargs: Any, 126 ) -> None: 127 super().__init__(**kwargs) 128 if self._do_init: 129 if name: 130 self.name = name 131 if style: 132 self.style = style 133 text = self.set_value_and_type( 134 value=value, value_type=value_type, text=text 135 ) 136 self.text = text # type: ignore
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
443class VarInitialCreator(Element): 444 _tag = "text:initial-creator" 445 _properties = (PropDef("fixed", "text:fixed"),) 446 447 def __init__(self, fixed: bool = False, **kwargs: Any) -> None: 448 super().__init__(**kwargs) 449 if self._do_init and fixed: 450 self.fixed = True
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
270class VarPageNumber(Element): 271 """ 272 select_page -- string in ('previous', 'current', 'next') 273 274 page_adjust -- int (to add or subtract to the page number) 275 """ 276 277 _tag = "text:page-number" 278 _properties = ( 279 PropDef("select_page", "text:select-page"), 280 PropDef("page_adjust", "text:page-adjust"), 281 ) 282 283 def __init__( 284 self, 285 select_page: str | None = None, 286 page_adjust: str | None = None, 287 **kwargs: Any, 288 ) -> None: 289 super().__init__(**kwargs) 290 if self._do_init: 291 if select_page is None: 292 select_page = "current" 293 self.select_page = select_page 294 if page_adjust is not None: 295 self.page_adjust = page_adjust
select_page -- string in ('previous', 'current', 'next')
page_adjust -- int (to add or subtract to the page number)
283 def __init__( 284 self, 285 select_page: str | None = None, 286 page_adjust: str | None = None, 287 **kwargs: Any, 288 ) -> None: 289 super().__init__(**kwargs) 290 if self._do_init: 291 if select_page is None: 292 select_page = "current" 293 self.select_page = select_page 294 if page_adjust is not None: 295 self.page_adjust = page_adjust
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
64class VarSet(ElementTyped): 65 _tag = "text:variable-set" 66 _properties = ( 67 PropDef("name", "text:name"), 68 PropDef("style", "style:data-style-name"), 69 PropDef("display", "text:display"), 70 ) 71 72 def __init__( 73 self, 74 name: str | None = None, 75 value: Any = None, 76 value_type: str | None = None, 77 display: str | bool = False, 78 text: str | None = None, 79 style: str | None = None, 80 **kwargs: Any, 81 ) -> None: 82 super().__init__(**kwargs) 83 if self._do_init: 84 if name: 85 self.name = name 86 if style: 87 self.style = style 88 text = self.set_value_and_type( 89 value=value, value_type=value_type, text=text 90 ) 91 if not display: 92 self.display = "none" 93 else: 94 self.text = text # type: ignore 95 96 def set_value(self, value: Any) -> None: 97 name = self.get_attribute("text:name") 98 display = self.get_attribute("text:display") 99 self.clear() 100 text = self.set_value_and_type(value=value) 101 self.set_attribute("text:name", name) 102 if display is not None: 103 self.set_attribute("text:display", display) 104 if isinstance(text, str): 105 self.text = text
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
72 def __init__( 73 self, 74 name: str | None = None, 75 value: Any = None, 76 value_type: str | None = None, 77 display: str | bool = False, 78 text: str | None = None, 79 style: str | None = None, 80 **kwargs: Any, 81 ) -> None: 82 super().__init__(**kwargs) 83 if self._do_init: 84 if name: 85 self.name = name 86 if style: 87 self.style = style 88 text = self.set_value_and_type( 89 value=value, value_type=value_type, text=text 90 ) 91 if not display: 92 self.display = "none" 93 else: 94 self.text = text # type: ignore
96 def set_value(self, value: Any) -> None: 97 name = self.get_attribute("text:name") 98 display = self.get_attribute("text:display") 99 self.clear() 100 text = self.set_value_and_type(value=value) 101 self.set_attribute("text:name", name) 102 if display is not None: 103 self.set_attribute("text:display", display) 104 if isinstance(text, str): 105 self.text = text
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
341class VarTime(Element): 342 _tag = "text:time" 343 _properties = ( 344 PropDef("time", "text:time-value"), 345 PropDef("fixed", "text:fixed"), 346 PropDef("data_style", "style:data-style-name"), 347 PropDef("time_adjust", "text:time-adjust"), 348 ) 349 350 def __init__( 351 self, 352 time: datetime, 353 fixed: bool = False, 354 data_style: str | None = None, 355 text: str | None = None, 356 time_adjust: timedelta | None = None, 357 **kwargs: Any, 358 ) -> None: 359 super().__init__(**kwargs) 360 if self._do_init: 361 self.time = DateTime.encode(time) 362 if fixed: 363 self.fixed = True 364 if data_style is not None: 365 self.data_style = data_style 366 if text is None: 367 text = time.strftime("%H:%M:%S") 368 self.text = text 369 if time_adjust is not None: 370 self.date_adjust = Duration.encode(time_adjust)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
350 def __init__( 351 self, 352 time: datetime, 353 fixed: bool = False, 354 data_style: str | None = None, 355 text: str | None = None, 356 time_adjust: timedelta | None = None, 357 **kwargs: Any, 358 ) -> None: 359 super().__init__(**kwargs) 360 if self._do_init: 361 self.time = DateTime.encode(time) 362 if fixed: 363 self.fixed = True 364 if data_style is not None: 365 self.data_style = data_style 366 if text is None: 367 text = time.strftime("%H:%M:%S") 368 self.text = text 369 if time_adjust is not None: 370 self.date_adjust = Duration.encode(time_adjust)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
394 def getter(self: Element) -> str | bool | None: 395 try: 396 if family and self.family != family: # type: ignore 397 return None 398 except AttributeError: 399 return None 400 value = self.__element.get(name) 401 if value is None: 402 return None 403 elif value in ("true", "false"): 404 return Boolean.decode(value) 405 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
37class XmlPart: 38 """Representation of an XML part. 39 40 Abstraction of the XML library behind. 41 """ 42 43 def __init__(self, part_name: str, container: Container) -> None: 44 self.part_name = part_name 45 self.container = container 46 47 # Internal state 48 self.__tree: _ElementTree | None = None 49 self.__root: Element | None = None 50 51 def _get_tree(self) -> _ElementTree: 52 if self.__tree is None: 53 part = self.container.get_part(self.part_name) 54 self.__tree = parse(BytesIO(part)) # type: ignore 55 return self.__tree 56 57 def __repr__(self) -> str: 58 return f"<{self.__class__.__name__} part_name={self.part_name}>" 59 60 # Public API 61 62 @property 63 def root(self) -> Element: 64 if self.__root is None: 65 tree = self._get_tree() 66 self.__root = Element.from_tag(tree.getroot()) 67 return self.__root 68 69 def get_elements(self, xpath_query: str) -> list[Element | Text]: 70 root = self.root 71 return root.xpath(xpath_query) 72 73 def get_element(self, xpath_query: str) -> Any: 74 result = self.get_elements(xpath_query) 75 if not result: 76 return None 77 return result[0] 78 79 def delete_element(self, child: Element) -> None: 80 child.delete() 81 82 def xpath(self, xpath_query: str) -> list[Element | Text]: 83 """Apply XPath query to the XML part. Return list of Element or 84 Text instances translated from the nodes found. 85 """ 86 root = self.root 87 return root.xpath(xpath_query) 88 89 @property 90 def clone(self) -> XmlPart: 91 clone = object.__new__(self.__class__) 92 for name in self.__dict__: 93 if name == "container": 94 setattr(clone, name, self.container.clone) 95 elif name in ("_XmlPart__tree",): 96 setattr(clone, name, None) 97 else: 98 value = getattr(self, name) 99 value = deepcopy(value) 100 setattr(clone, name, value) 101 return clone 102 103 def serialize(self, pretty: bool = False) -> bytes: 104 tree = self._get_tree() 105 # Lxml declaration is too exotic to me 106 data = [b'<?xml version="1.0" encoding="UTF-8"?>'] 107 bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8") 108 # Lxml with pretty_print is adding a empty line 109 if pretty: 110 bytes_tree = bytes_tree.strip() 111 data.append(bytes_tree) 112 return b"\n".join(data)
Representation of an XML part.
Abstraction of the XML library behind.
82 def xpath(self, xpath_query: str) -> list[Element | Text]: 83 """Apply XPath query to the XML part. Return list of Element or 84 Text instances translated from the nodes found. 85 """ 86 root = self.root 87 return root.xpath(xpath_query)
Apply XPath query to the XML part. Return list of Element or Text instances translated from the nodes found.
89 @property 90 def clone(self) -> XmlPart: 91 clone = object.__new__(self.__class__) 92 for name in self.__dict__: 93 if name == "container": 94 setattr(clone, name, self.container.clone) 95 elif name in ("_XmlPart__tree",): 96 setattr(clone, name, None) 97 else: 98 value = getattr(self, name) 99 value = deepcopy(value) 100 setattr(clone, name, value) 101 return clone
103 def serialize(self, pretty: bool = False) -> bytes: 104 tree = self._get_tree() 105 # Lxml declaration is too exotic to me 106 data = [b'<?xml version="1.0" encoding="UTF-8"?>'] 107 bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8") 108 # Lxml with pretty_print is adding a empty line 109 if pretty: 110 bytes_tree = bytes_tree.strip() 111 data.append(bytes_tree) 112 return b"\n".join(data)
236def create_table_cell_style( 237 border: str | None = None, 238 border_top: str | None = None, 239 border_bottom: str | None = None, 240 border_left: str | None = None, 241 border_right: str | None = None, 242 padding: str | None = None, 243 padding_top: str | None = None, 244 padding_bottom: str | None = None, 245 padding_left: str | None = None, 246 padding_right: str | None = None, 247 background_color: str | tuple | None = None, 248 shadow: str | None = None, 249 color: str | tuple | None = None, 250) -> Style: 251 """Return a cell style. 252 253 The borders arguments must be some style attribute strings or None, see the 254 method 'make_table_cell_border_string' to generate them. 255 If the 'border' argument as the value 'default', the default style 256 "0.06pt solid #000000" is used for the 4 borders. 257 If any value is used for border, it is used for the 4 borders, else any of 258 the 4 borders can be specified by it's own string. If all the border, 259 border_top, border_bottom, ... arguments are None, an empty border is used 260 (ODF value is fo:border="none"). 261 262 Padding arguments are string specifying a length (e.g. "0.5mm")". If 263 'padding' is provided, it is used for the 4 sides, else any of 264 the 4 sides padding can be specified by it's own string. Default padding is 265 no padding. 266 267 Arguments: 268 269 border -- str, style string for borders on four sides 270 271 border_top -- str, style string for top if no 'border' argument 272 273 border_bottom -- str, style string for bottom if no 'border' argument 274 275 border_left -- str, style string for left if no 'border' argument 276 277 border_right -- str, style string for right if no 'border' argument 278 279 padding -- str, style string for padding on four sides 280 281 padding_top -- str, style string for top if no 'padding' argument 282 283 padding_bottom -- str, style string for bottom if no 'padding' argument 284 285 padding_left -- str, style string for left if no 'padding' argument 286 287 padding_right -- str, style string for right if no 'padding' argument 288 289 background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 290 291 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 292 293 color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 294 295 Return : Style 296 """ 297 if border == "default": 298 border = make_table_cell_border_string() # default border 299 if border is not None: 300 # use the border value for 4 sides. 301 border_bottom = border_top = border_left = border_right = None 302 if ( 303 border is None 304 and border_bottom is None 305 and border_top is None 306 and border_left is None 307 and border_right is None 308 ): 309 border = "none" 310 if padding is not None: 311 # use the padding value for 4 sides. 312 padding_bottom = padding_top = padding_left = padding_right = None 313 if color: 314 color_string = _make_color_string(color) 315 if background_color: 316 bgcolor_string = _make_color_string(background_color) 317 else: 318 bgcolor_string = None 319 cell_style = Style( 320 "table-cell", 321 area="table-cell", 322 border=border, 323 border_top=border_top, 324 border_bottom=border_bottom, 325 border_left=border_left, 326 border_right=border_right, 327 padding=padding, 328 padding_top=padding_top, 329 padding_bottom=padding_bottom, 330 padding_left=padding_left, 331 padding_right=padding_right, 332 background_color=bgcolor_string, 333 shadow=shadow, 334 ) 335 if color: 336 cell_style.set_properties(area="text", color=color_string) 337 return cell_style
Return a cell style.
The borders arguments must be some style attribute strings or None, see the method 'make_table_cell_border_string' to generate them. If the 'border' argument as the value 'default', the default style "0.06pt solid #000000" is used for the 4 borders. If any value is used for border, it is used for the 4 borders, else any of the 4 borders can be specified by it's own string. If all the border, border_top, border_bottom, ... arguments are None, an empty border is used (ODF value is fo:border="none").
Padding arguments are string specifying a length (e.g. "0.5mm")". If 'padding' is provided, it is used for the 4 sides, else any of the 4 sides padding can be specified by it's own string. Default padding is no padding.
Arguments:
border -- str, style string for borders on four sides
border_top -- str, style string for top if no 'border' argument
border_bottom -- str, style string for bottom if no 'border' argument
border_left -- str, style string for left if no 'border' argument
border_right -- str, style string for right if no 'border' argument
padding -- str, style string for padding on four sides
padding_top -- str, style string for top if no 'padding' argument
padding_bottom -- str, style string for bottom if no 'padding' argument
padding_left -- str, style string for left if no 'padding' argument
padding_right -- str, style string for right if no 'padding' argument
background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
Return : Style
1163def default_currency_style() -> Element: 1164 return Element.from_tag( 1165 """<number:currency-style style:name="lpod-default-currency-style"> 1166 <number:text>-</number:text> 1167 <number:number number:decimal-places="2" 1168 number:min-integer-digits="1" 1169 number:grouping="true"/> 1170 <number:text> </number:text> 1171 <number:currency-symbol 1172 number:language="fr" 1173 number:country="FR">€</number:currency-symbol> 1174 </number:currency-style>""" 1175 )
1142def default_date_style() -> Element: 1143 return Element.from_tag( 1144 """ 1145 <number:date-style style:name="lpod-default-date-style"> 1146 <number:year number:style="long"/> 1147 <number:text>-</number:text> 1148 <number:month number:style="long"/> 1149 <number:text>-</number:text> 1150 <number:day number:style="long"/> 1151 </number:date-style>""" 1152 )
42def default_frame_position_style( 43 name: str = "FramePosition", 44 horizontal_pos: str = "from-left", 45 vertical_pos: str = "from-top", 46 horizontal_rel: str = "paragraph", 47 vertical_rel: str = "paragraph", 48) -> Style: 49 """Helper style for positioning frames in desktop applications that need 50 it. 51 52 Default arguments should be enough. 53 54 Use the returned Style as the frame style or build a new graphic style 55 with this style as the parent. 56 """ 57 return Style( 58 family="graphic", 59 name=name, 60 horizontal_pos=horizontal_pos, 61 horizontal_rel=horizontal_rel, 62 vertical_pos=vertical_pos, 63 vertical_rel=vertical_rel, 64 )
Helper style for positioning frames in desktop applications that need it.
Default arguments should be enough.
Use the returned Style as the frame style or build a new graphic style with this style as the parent.
1119def default_percentage_style() -> Element: 1120 return Element.from_tag( 1121 """<number:percentage-style 1122 style:name="lpod-default-percentage-style"> 1123 <number:number number:decimal-places="2" 1124 number:min-integer-digits="1"/> 1125 <number:text>%</number:text> 1126 </number:percentage-style>""" 1127 )
1130def default_time_style() -> Element: 1131 return Element.from_tag( 1132 """<number:time-style style:name="lpod-default-time-style"> 1133 <number:hours number:style="long"/> 1134 <number:text>:</number:text> 1135 <number:minutes number:style="long"/> 1136 <number:text>:</number:text> 1137 <number:seconds number:style="long"/> 1138 </number:time-style>""" 1139 )
150def default_toc_level_style(level: int) -> Style: 151 """Generate an automatic default style for the given TOC level.""" 152 tab_stop = TabStopStyle(style_type="right", leader_style="dotted", leader_text=".") 153 position = 17.5 - (0.5 * level) 154 tab_stop.style_position = f"{position}cm" 155 tab_stops = Element.from_tag("style:tab-stops") 156 tab_stops.append(tab_stop) 157 properties = Element.from_tag("style:paragraph-properties") 158 properties.append(tab_stops) 159 toc_style_level = Style( 160 family="paragraph", 161 name=_toc_entry_style_name(level), 162 parent=f"Contents_20_{level}", 163 ) 164 toc_style_level.append(properties) 165 return toc_style_level
Generate an automatic default style for the given TOC level.
120def hex2rgb(color: str) -> tuple[int, int, int]: 121 """Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) 122 tuple. 123 Arguments: 124 125 color -- str 126 127 Return: tuple 128 """ 129 code = color[1:] 130 if not (len(color) == 7 and color[0] == "#" and code.isalnum()): 131 raise ValueError('"%s" is not a valid color' % color) 132 red = int(code[:2], 16) 133 green = int(code[2:4], 16) 134 blue = int(code[4:6], 16) 135 return (red, green, blue)
Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) tuple. Arguments:
color -- str
Return: tuple
216def make_table_cell_border_string( 217 thick: str | float | int | None = None, 218 line: str | None = None, 219 color: str | tuple | None = None, 220) -> str: 221 """Returns a string for style:table-cell-properties fo:border, 222 with default : "0.06pt solid #000000" 223 224 thick -- str or float or int 225 line -- str 226 color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 227 228 Returns : str 229 """ 230 thick_string = _make_thick_string(thick) 231 line_string = _make_line_string(line) 232 color_string = _make_color_string(color) 233 return " ".join((thick_string, line_string, color_string))
Returns a string for style:table-cell-properties fo:border, with default : "0.06pt solid #000000"
thick -- str or float or int
line -- str
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
Returns : str
138def rgb2hex(color: str | tuple[int, int, int]) -> str: 139 """Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" 140 hexadecimal representation. 141 Arguments: 142 143 color -- str or tuple 144 145 Return: str 146 147 Examples:: 148 149 >>> rgb2hex('yellow') 150 '#FFFF00' 151 >>> rgb2hex((238, 130, 238)) 152 '#EE82EE' 153 """ 154 if isinstance(color, str): 155 try: 156 code = CSS3_COLORMAP[color.lower()] 157 except KeyError as e: 158 raise KeyError(f'Color "{color}" is unknown') from e 159 elif isinstance(color, tuple): 160 if len(color) != 3: 161 raise ValueError("Color must be a 3-tuple") 162 code = color 163 else: 164 raise TypeError("Invalid color") 165 for channel in code: 166 if channel < 0 or channel > 255: 167 raise ValueError("Color code must be between 0 and 255") 168 return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"
Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" hexadecimal representation. Arguments:
color -- str or tuple
Return: str
Examples::
>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'